I have a Spring Boot project which has multiple Model class
I want to use multiple model classes in my DAO implementation.
Can someone please explain how can I use HQL queries here.
Most of the examples i saw uses CRUDRepository. But in my understanding this limits us to one model class (please correct me if my understanding is wrong).
Secondly using CRUDRepository limits me to very specific methods. But HQL queries I need could be more complex like:
Query query = session.createQuery(from Employee where name=:name and place=:place and phone=:phone and gender=:gender);
Is it standard practice to use CRUDRepository even for HQL queries like this.
Otherwise how can I use such HQL queries. (If possible please also add what all dependencies and annotations i will need to add). I am using Hibernate and postgres.
If you wish to execute dynamic HQL queries without using repositories, etc, this works for me really nicely:
#Autowired
EntityManager entityManager;
#RequestMapping("/query")
#ResponseBody
public String testQuery() {
Query query = entityManager.createQuery("select u from User u");
List<User> users = query.getResultList();
users.forEach(u -> System.out.println(u.getFirstname()));
return "See Console";
}
The nice thing about it is that if you're quickly wanting to test a query, and you're using something like Spring LiveReload, JRebel, DCEVM or HotSwap with Hybris, you can change the query and save and refresh.
CRUD repositories are the standard way to write a DAO. If you look at the documentations, you will see that you can have complex queries that uses the fields of the entity (which can be pretty complex, I encourage you to look here at the query methods part - 4.3), and if you want even more complex methods, you can use the annotation #Query over a method:
#Repository
interface MyRepo implements JpaRepository<MyEntity,Long> {
#Query("your custom query here")
MyEntity findByFeature(String a)
}
Now you can insert you query and access you String a inside the query (like Select u where u.name="blabla" where blabla is your input string)
The #Query comes with the same dependency as the repositories (I think that spring-boot-starter-data-jpa is enough)
JpaRepository is surely awesome to use, but for someone who has to use complex HQL queries (due to your organization's recommendations etc), here is how i did it:
Get Session factory by injecting following bean in your configuration:
#Bean
public SessionFactory sessionFactory(#Qualifier("entityManagerFactory") EntityManagerFactory emf)
{
return emf.unwrap(SessionFactory.class);
}
Add EntityScan and ComponentScan to your configuration class
#EntityScan(basePackages = { "com.app.persistence" })
#ComponentScan(basePackages = { "com.app" })
Go ahead and use any HQL queries:
#Autowired
SessionFactory factory;
#Override
public String check() {
Session session = null;
try {
session = factory.openSession();
Query query = session.createQuery("from Test");
List<Test> res = query.list();
Test sing = res.get(0);
return sing.getName();
} catch (Exception e) {
System.out.println("Exception in Dao");
e.printStackTrace();
throw e;
} finally {
if (session != null) {
session.close();
}
}
}
Note: I am assuming that other things like configuring DataSource and all have been taken care already.
Anyone can feel free to correct me if my understanding or way was wrong.
But it worked well for me this way!! :)
Related
I have been very impressed with Javers. Unfortunately, I am currently on a project that features poorly designed legacy code. I seek guidance in the application of JaversAuditable, JaversAuditableDelete, and JaversAuditableConditionalDelete annotations under the following scenarios:
Suppose my DAOs are using hibernate's update and merge methods. Should I adorn these DAO methods with any of the above annotations?
Suppose now that I have malformed DAO methods like the following:
public Group createNewGroup(String title, String desc, String color, int type) {
Group group = new Group();
group.setGroupTitle(title);
group.setGroupDesc(desc);
group.setGroupColor(color);
group.setType(type);
group.setCategory(Group.CATEGORY_GROUP);
getHibernateTemplate().save(group);
return group;
}
or
public void updateAlerts(Long userId) {
final String hqlUpdate = "update Alert a set a.acknowleged = true where a.systemUser.systemUserId = ?0";
try {
getHibernateTemplate().bulkUpdate(hqlUpdate, userId);
} catch(Exception e){
throw new RuntimeException(e);
}
}
are any of the annotations applicable to these methods?
I expect from examination of the JaversCommitAdvice class that the aforementioned annotations will not work. I would like to know what I can do short of manually calling Javers.commit() in each of these malformed DAO methods.
Can we pass or input a .sql file to the Query annotation in Spring JPA?
Something like this
#Query(file:/sql/employeeList.sql , nativeQuery=true)
/sql/employeeList.sql - i would like to put this file somewhere in the code, which will have the select sql in it.
I am not looking to have native sql string
#Query("select abc....", nativeQuery=true) -- I dont want to use like this. because my sql is too huge.
#Jens Schauder is right you can't specify a sql file in Spring Data Jpa but as an alternative solution you could use this library https://github.com/VEINHORN/spring-data-sqlfile or you could simply load the file on your own.
Note: im using lombok on this example
#Component
#Slf4j
#AllArgsConstructor
public class ArticleBatchRepository {
#PersistenceContext
EntityManager em;
public List<ArticleBatch> findAll(String articleId) {
Query nativeQuery = em.createNativeQuery(loadQuery("/db/queries/articles/BatchByWarehouse.sql"), ArticleBatch.class);
nativeQuery.setParameter(1, articleId);
return (List<ArticleBatch>) nativeQuery.getResultList();
}
private String loadQuery(String path) {
try {
InputStream is = this.getClass().getResourceAsStream(path);
java.util.Scanner s = new java.util.Scanner(is).useDelimiter("\\A");
return s.hasNext() ? s.next() : "";
} catch (Exception e) {
log.error("Failed to load query {}: {}", path, e.getMessage(), e);
return null;
}
}
}
You can't specify a sql file but Spring Data JPA will look in /META-INF/jpa-named-queries.properties
to find named queries, which allows you to specify queries in a sql file.
This is described in this answer to a similar question.
You can check this repository.
You need to provide a JpaQueryMethodFactory bean to your configuration. You can extend the JpaQueryMethod class and override the getAnnotatedQuery and getRequiredAnnotatedQuery functions to read queries from the file.
In Spring Data, how can I append more conditions to an existing query?
For example, I have the CrudRepository below:
#RepositoryRestResource
public interface MyRep extends CrudRepository<MyObject, Long> {
#Query("from MyObject mo where mo.attrib1 = :attrib1")
List<MyObj> findMyObjects(String attrib1, String conditions);
}
At runtime, I will need to call "findMyObjects" with two params. The first param is obviously the value of attrib1. the second param will be a where clause that would be determined at runtime, for example "attrib2 like '%xx%' and attrib3 between 'that' and 'this' and ...". I know this extra where condition will be valid, but I don't know what attributes and conditions will be in it. Is there anyway to append this where clause to the query defined in the #Query annotation?
Unfortunately, no. There is no straightforward way to achieve that.
You'll want to use custom reporistory methods where you'll be able to inject an EntityManager and interact with EntityManager.createQuery(...) directly.
Alternatively, you can build dynamic queries using Specifications or QueryDsl.
I ended up injecting an EntityManager that I obtained in the rest controller. Posting what I did here for criticism:
The repository code:
#RepositoryRestResource
public interface MyRepo extends CrudRepository<MyObject, Long> {
default List<MyObject> findByRuntimeConditions(EntityManager em, String runtimeConditions) {
String mySql = "<built my sql here. Watch for sql injection.>";
List<MyObject> list = em.createQuery(mySql).getResultList();
return list
}
}
The Rest controller code:
#RestController
public class DataController {
#Autowired
EntityManager em;
// of course watch for sql injection
#RequestMapping("myobjects/{runtimeConditions}")
public List<MyObject> getMyObjects(#PathVariable String runtimeConditions) {
List<MyObject> list = MyRepo.findByRuntimeConditions(em, runtimeConditions);
return list;
}
}
I try to establish a deleteAll function which deletes all documents which are associated with a given repository and class. In order to this I created a custom N1ql query. But I want the Couchbase index to be updated before later database operations take place. My guess is that I have to change the consistency level of the query to achieve this behaviour.
Here and here I found some examples which do this by using the CouchbaseTemplate. But my template is null. Could anybody tell me what I am doing wrong?
public void deleteAll() throws DBException {
CouchbaseOperations couchbaseTemplate;
try {
couchbaseTemplate = templateProvider.resolve(getRepository().getClass(), getClassName().getClass());
} catch (Exception e) {
throw new DBException("Could not get couchbase client", e);
}
String statement = String.format("DELETE FROM %s WHERE _class='%s'",
couchbaseTemplate.getCouchbaseBucket().name(), getClassName());
ScanConsistency consistency = couchbaseTemplate.getDefaultConsistency().n1qlConsistency();
N1qlParams queryParams = N1qlParams.build().consistency(consistency);
N1qlQuery query = N1qlQuery.simple(statement, queryParams);
N1qlQueryResult result = couchbaseTemplate.queryN1QL(query);
//Result handling
}
}
The templateProvider is autowired.
It is not entirely clear about your repository and entity from your code snippet. Which version of SDC are you using?
If you are using operation mapping bean, you get the underlying couchbase template for the particular repository and entity using
#Repository
public interface MyRepository extends CrudRepository<MyEntity, String> {
}
public class MyService {
#Autowired
MyRepository repo;
#Autowired
RepositoryOperationsMapping templateProvider;
....
CouchbaseOperations operations = templateProvider.resolve(repo.getClass(),MyEntity.class);
Make sure to enable couchbase repositories with #EnableCouchbaseRepositories. If your repositories only use couchbase, you can also get the couchbase template bean directly.
#Autowired
CouchbaseTemplate template;
I am upgrading a working project from Spring2+Hibernate3 to Spring3+Hibernate4. Since HibernateTemplate and HibernateDAOSupport have been retired, I did the following
Before (simplified)
public List<Object> loadTable(final Class<?> cls)
{
Session s = getSession(); // was calling the old Spring getSession
Criteria c = s.createCriteria(cls);
List<Object> objects = c.list();
if (objects == null)
{
objects = new ArrayList<Object>();
}
closeSession(s);
return objects;
}
Now (simplified)
#Transactional(propagation=Propagation.REQUIRED)
public List<Object> loadTable(final Class<?> cls)
{
Session s = sessionFactory.getCurrentSession();
Criteria c = s.createCriteria(cls);
List<Object> objects = c.list();
if (objects == null)
{
objects = new ArrayList<Object>();
}
return objects;
}
I also added the transaction annotation declaration to Spring XML and removed this from Hibernate properties
"hibernate.current_session_context_class", "org.hibernate.context.ThreadLocalSessionContext"
The #Transactional annotation seems to have worked as I see this in the stacktrace
at com.database.spring.DatabaseDAOImpl$$EnhancerByCGLIB$$7d20ef95.loadTable(<generated>)
During initialization, the changes outlined above seem to work for a few calls to the loadTable function but when it gets around to loading an entity with a parent, I get the "collection with cascade="all-delete-orphan" was no longer referenced" error. Since I have not touched any other code that sets/gets parents or children and am only trying to fix the DAO method, and the query is only doing a sql SELECT, can anyone see why the code got broken?
The problem seems similar to Spring transaction management breaks hibernate cascade
This is unlikely problem of Spring, but rather issue with your entity handling / definition. When you are using deleteOrphans on a relation, the underlying PersistentSet MUST NOT be removed from the entity itself. You are allowed only to modify the set instance itself. So if you are trying to do anything clever within your entity setters, that is the cause.
Also as far as I remember there are some issues when you have deleteOrphans on both sides of the relation and/or load/manipulate both sides within one session.
Btw. I don't think "hibernate.current_session_context_class", "org.hibernate.context.ThreadLocalSessionContext" is necessary. In our project, this is the only configuration we have:
#Bean
public LocalSessionFactoryBuilder sessionFactoryBuilder() {
return ((LocalSessionFactoryBuilder) new LocalSessionFactoryBuilder(
dataSourceConfig.dataSource()).scanPackages(ENTITY_PACKAGES).
setProperty("hibernate.id.new_generator_mappings", "true").
setProperty("hibernate.dialect", dataSourceConfig.dialect()).
setProperty("javax.persistence.validation.mode", "none"));
}
#Bean
public SessionFactory sessionFactory() {
return sessionFactoryBuilder().buildSessionFactory();
}
The issue was with Session Management. The same block of transactional code was being called by other modules that were doing their own session handling. To add to our woes, some of the calling modules were Spring beans while others were written in direct Hibernate API style. This disorganization was sufficient work to keep us away from moving up to Hibernate 4 immediately.
Moral of the lesson (how do you like that English?): Use a consistent DAO implementation across the entire project and stick to a clearly defined session and transaction management strategy.