Question
What is the best practice for the type of paramaters of dao/repository method, entity-objects or entity-ids?
Example code
#Entity
class Product {
// ...
#ManyToOne
Seller seller;
}
#Entity
class Seller {
#Id #GeneratedValue
Long id;
}
class ProductDao {
// ...
// Using ids
public List<Product> getProductsOf(long sellerId) {
return getSession()
.createQuery("from Product where seller.id = ?")
.setLong(0, sellerId)
.list();
}
// Using object-references
public List<Product> getProductsOf(Seller seller) {
return getSession()
.createQuery("from Product where seller = ?")
.setEntity(0, seller)
.list();
}
// Using object-references using merge() on a detached object
public List<Product> getProductsOf2(Seller seller) {
Seller persistentSeller = getSession().merge(seller);
return getSession()
.createQuery("from Product where seller = ?")
.setEntity(0, seller)
.list();
}
// Using object-references using lock() on a detached object
public List<Product> getProductsOf3(Seller seller) {
getSession().buildLockRequest(LockOptions.NONE).lock(seller);
return getSession()
.createQuery("from Product where seller = ?")
.setEntity(0, seller)
.list();
}
}
Pros and cons
I have found the following pros and cons, but I was wondering if there is a best practice among experienced Spring/Hibernate/JPA users.
Pros of: getProductsOf(Seller seller)
Easy to use from a client perspective when you already have a seller that is in the persistent context (persistent state).
Cons of: getProductsOf(Seller seller)
You have to verify that seller is in persistent or detached state, which can make its implementation verbose. You'll have to use merge() or locking, see getProductsOf2() and getProductsOf3().
Even if you know the id of the seller, you first have to query the seller object separatly. (load() can be used to use a proxy instead to avoid the extra query to Seller, but you still have to call the session object.)
the parameter can be null.
Pros of: getProductsOf(long sellerId)
When you don't have a seller object yet, but know the sellerId, this may be faster when you only need to query for a sellerId once in the unit-of-work.
Avoids the null reference problems
Cons of: getProductsOf(long sellerId)
When multiple "long" parameters exist in the method, you may mistake in the argument call order, causing you to query using wrong ids.
Feels like a less object-oriented approach than using an object as parameter.
The method call looks less clean:
getProductsOf(seller.getId())
instead of:
getProductsOf(seller)
My preference
I'm using getProductsOf(Seller seller), but having to verify whether seller is in persistent or detached state is very cumbersome. Therefor, I'm thinking of using ids instead.
The best way is to avoid writing down your own DAO and use Spring Data instead.
I prefer the Spring Repository API, which looks like this:
public interface CrudRepository<T, ID extends Serializable>
extends Repository<T, ID> {
<S extends T> S save(S entity);
T findOne(ID primaryKey);
Iterable<T> findAll();
Long count();
void delete(T entity);
boolean exists(ID primaryKey);
}
the save and delete methods take an entity
the findOne and exists take an identifier because it assumes you don't have the entity that you want to fetch
As for findOne, it's better to have it take an identifier. This way you can call it even if you have an entity. If it were taken an entity, then you'd have to create a transient entity with a populated identifier just for the sake of fetching the associated managed entity.
Why not have them both?
public List<Product> getProductsOf(long sellerId){
return getSession()
.createQuery("from Product where seller.id = ?")
.setLong(0, sellerId)
.list();
}
public List<Product> getProductsOf(Seller seller){
return getProductsOf(seller.getId());
}
On a side note, I prefer to use classes instead of native types for ids because that way you can verify if the object is new (seller.getId()==null) and call either persist() or merge().
Related
public interface UserDao {
User getUserById(Long id);
void saveUser(User user);
List<UserDto> getAllUsers();
boolean isExistUserByEmail(String email);
boolean isExistUserByUserName(String userName);
// get users by id list
List<User> getUsersByIdIn(List<Long> idList);
// get all active users
List<UserDto> getAllActiveUsers();
User getUserByEmail(String email);
void saveAllUsers(List<User> userList);
List<Object[]> getAllInstructors();
}
This my dao impl method
#Override
public List<Object[]> getAllInstructors() {
return userRepository.getAllInstructors();
}
This is the query from my repository layer
#Query(value = "select distinct u.first_name, u.last_name, u.full_name, u.id from public.user u inner join public.users_group ug on u.id = ug.user_id and ug.group_list_id=1 WHERE status=1 ORDER BY u.id DESC", nativeQuery = true)
This is the method in my controller layer
#GetMapping("/instructors")
public List<Object[]> getAllIntsructors() {
return userService.getAllInstructors();
}
Result when I call the api on postman
The result I expect is:
first_name: "Iresha"
second_name: "Vishwakala"
But I don't get the key. I only get an array of objects showing me the values.
The way you are using your Repo is not a good practice. To be honest I have never seen it before.
This would be the correct way to do it:
public interface UserRepository extends JpaRepository<UserEntity, Long> // Here the first is the Entity which will be fetched by this repo, and the second is the type of ID that this Entity has.
{
// I am supposing that your entity is UserEntity and has an ID of type Long.
// Here come the queries which must return primitives or UserEntity (list, set etc.)
// An example query would be:
#Query(value = "....", nativeQuery = true) List<UserEntity> getUsersAsYouWant();
}
The JpaRepository makes use of Java Generics (hence the <>), that is why you must supply the class the types that it has to map the DB rows/data to. If you want to return specific types/Dtos/List you may use the same Repo but with different Query building (not native but JPQL). But that would be yet another question. If you want to check more, see my answer here.
I have a development project using Spring Data JPA and MapStruct to map between Entities and DTOs. Last week I decided it was time to address the FetchType.EAGER vs LAZY issue I have postponed for some time. I choose to use #NamedEntityGraph and #EntityGraph to load properties when needed. However I am stuck with this LazyInitializationExeption problem when doing the mapping from entity to dto. I think I know where this happens but I do not know how to get passed it.
The code
#NamedEntityGraph(name="Employee.full", ...)
#Entity
public class Employee {
private Set<Role> roles = new HashSet<>();
}
#Entity
public class Role {
private Set<Employee> employees = new HashSet<>();
}
public interface EmployeeRepository extends JpaRepository<Employee, Long> {
#EntityGraph(value = "Employee.full")
#Override
Page<Employee> findAll(Pageable pageable);
}
#Service
public class EmployeeService {
public Page<EmployeeDTO> findAll(PageRequest pageRequest) {
Page<Employee> employees = repository.findAll(pageRequest); // ok
Page<EmployeeDTO> dtos = employees.map(emp -> mapper.toDTO(emp, new CycleAvoidMappingContext()); // this is where the exception happens
return dtos;
}
}
// also there is EmployeeDTO and RoleDTO classes mirroring the entity classes
// and there is a simple interface EmployeeMapper loaded as a spring component
// without any special mappings. However CycleAvoidingMappingContext is used.
I have tracked down the LazyInitializationException to happen when the mapper tries to map the roles dependency. The Role object do have Set<Employee> and therefore there is a cyclic reference.
When using FetchType.EAGER new CycleAvoidingMappingContext() solved this problem, but with LAZY this no longer works.
Does anybody know how I can avoid the exception and at the same time get my DTOs mapped correctly?
The problem is that when the code returns from findAll the entities are not managed anymore. So you have a LazyInitializationException because you are trying, outside of the scope of the session, to access a collection that hasn't been initialized already.
Adding eager make it works because it makes sure that the collection has been already initialized.
You have two alternatives:
Using an EAGER fetch;
Make sure that the entities are still managed when you return from the findAll. Adding a #Transactional to the method should work:
#Service
public class EmployeeService {
#Transactional
public Page<EmployeeDTO> findAll(PageRequest pageRequest) {
Page<Employee> employees = repository.findAll(pageRequest);
Page<EmployeeDTO> dtos = employees.map(emp -> mapper.toDTO(emp, new CycleAvoidMappingContext());
return dtos;
}
}
I would say that if you need the collection initialized, fetching it eagerly (with an entity graph or a query) makes sense.
Check this article for more details on entities states in Hibernate ORM.
UPDATE: It seems that this error happens because Mapstruct is converting the collection even if you don't need it in the DTO.
In this case, you have different options:
Remove the field roles from the DTO. Mapstruct will ignore the field in the entity because the DTO doesn't have a field with the same name;
Create a different DTO class for this specific case without the field roles;
Use the #Mapping annotation to ignore the field in the entity:
#Mapping(target = "roles", ignore = true)
void toDTO(...)
or, if you need the toDTO method sometimes
#Mapping(target = "roles", ignore = true)
void toSkipRolesDTO(...) // same signature as toDTO
I have One to many relationship Owner->Dog.
I am to query the dog by ID as well bring the Owner in EAGER way I have set this code using Hibernate 4.1.6 not XML mapping is used. i only need some fields from DOG and OWNER using Projections
the SQL being generated by Hibernate is perfect but my objects is not being populate because DOG is returning the fields populated but owner is returned DOG.OWNER==NULL here is the code i am using so far...
My entities.other code is omit by brevity
#Entity
public class Owner implements Serializable
{
Set<Dog>dogs=new HashSet<Dogs>(0);
#OneToMany(fetch=FetchType.LAZY, mappedBy="owner")
public Set<Dogs> getDogs(){return this.dogs}
}
#Entity
public class Dog implements Serializable
{
private Owner owner;
#ManyToOne(fetch=FetchType.LAZY)
#JoinColumn(name="ownerid")
public Owner getOwner(){return this.owner;}
}
here is my method.
public Dog getDogAndOwnerById(Integer dogId)
{
Projection p=Projections.projectionList()
.add(Projections.property("d.id"),"id")
.add(Projections.property("o.id"),"id");//and others fields
Session session = getHibernateTemplate().getSessionFactory().openSession();
Criteria like = session.createCriteria(Dog.class,"d")
.add(Restrictions.idEq(dogId)).setProjection(p)
.setResultTransformer(Transformers.aliasToBean(Dog.class))
.setFetchMode("owner",FetchMode.JOIN).createAlias("owner","o");
Dog dog = (Dog)like.uniqueResult();
//Object[]obj=(Object[])like.uniqueResult(); for(Object id:obj)System.out.println(id);
//System.out.println(obj.length);
session.close();
return dog;
//dog is OK BUT dog.OWNER is null.
}
the query is perfect here is the SQL
select
this_.ID as y0_,
owner_.ID as y1_
from
dog this_
inner join
owner owner_
on this_.ownerid=owner_.ID
where
and this_.ID = ?
my problem is... Dog instance is NOT null and all the fields are O.K meanwhile Dog.Owner is returnig null I have try this not using any Transformers.
Object[]obj=(Object[])like.uniqueResult(); for(Object id:obj)System.out.println(id);
System.out.println(obj.length);
And I can see the data correct what Hibernate is not returning my objects right? What I am doing wrong.
any help is hugely appreciate.
[update]
if i use this
Projection p=Projections.projectionList()
.add(Projections.property("d.id"),"id")
.add(Projections.property("o.status"),"status");
and status belongs to both tables the DOG entity is populate the other is not.
if i use
Projection p=Projections.projectionList()
.add(Projections.property("d.id"),"id")
.add(Projections.property("o.address"),"address");
and adress belong only to owner exception is thrown.
Exception in thread "main" org.hibernate.PropertyNotFoundException:
Could not find setter for address on class com.generic.model.Dog
Seems that Hibernate ALWAYS return at maximun 1 entity populate they can't populate both tables[selected columns] into objects[selected objects]?
I wrote a ResultTransformer that can solve your problem. It's name is AliasToBeanNestedResultTransformer, check it out on github.
i follow this post Complex Hibernate Projections and as result i write my own Transformer because resultTransformer alias to bean in hibernate in one level deep.
here is my simple resultTransformer
public class MyOwnTransformer implements ResultTransformer
{
#Override//the same order in projection list properties is the same returned by data array...
public Dog transformTuple(Object[]data,String[]alias)
{return new Dog((Integer)data[0],new Owner((Integer)data[1]));}
#Override
public List transformList(List dogs){return dogs;}//nothing to do here....
}
and in my criteria i fix the code like this.
public Dog getDogAndOwnerById(Integer dogId)
{
Projection p=Projections.projectionList()
.add(Projections.property("d.id"))//i dont need the alias anymore..
.add(Projections.property("o.id"));
Session session = getHibernateTemplate().getSessionFactory().openSession();
Criteria like = session.createCriteria(Dog.class,"d")
.add(Restrictions.idEq(dogId)).setProjection(p)
.setResultTransformer(new MyOwnTransformer())
.setFetchMode("owner",FetchMode.JOIN).createAlias("owner","o");
Dog dog = (Dog)like.uniqueResult();
session.close();
return dog;
}
i hope it helps somebody.
I have a java web application built using spring+hibernate.
I have code like this:
for (Account account : accountList){
Client client = clientService.findById(account.getFkClient()); // fkClient is foreign key to Client
if (client != null) {
...
anObject.setName(client.getName());
anObject.setAccountNo(account.getAccountNo());
...
}
else {
...
anObject.setAccountNo(account.getAccountNo());
...
}
...
}
accountList is a List of Account entity that retrieved from hibernate call. Inside the for loop, a Client entity is retrieved from account using hibernate call inside clientService.findById method.
These are the class involved to the call:
public class ClientService implements IClientService {
private IClientDAO clientDAO;
...
#Override
public Client findById(Long id) throws Exception {
return clientDAO.findById(id);
}
}
public class ClientDAO extends AbstractHibernateDAO<Client, Long> implements IClientDAO {
#Override
public Client findById(Long id) throws Exception {
return super.findById(id);
}
}
public class AbstractHibernateDAO<T,Y extends Serializable> extends HibernateDaoSupport {
protected Class<T> domainClass = getDomainClass();
private Class<T> getDomainClass() {
if (domainClass == null) {
ParameterizedType thisType = (ParameterizedType) getClass().getGenericSuperclass();
domainClass = (Class<T>) thisType.getActualTypeArguments()[0];
}
return domainClass;
}
public T findById(final Y id) throws SystemException {
return (T) this.execute(new HibernateCallback<T>() {
#Override
public T doInHibernate(Session session) throws HibernateException, SQLException {
return (T) session.get(domainClass, id);
}
});
}
}
Note: clientService and clientDAO are spring beans object.
My question is how to optimize the clientService.findById inside the loop with hibernate? I feel the findById call make the looping process become slower.
The accountList usually contains 7000+ records, so I need something like pre-compiled query mechanism just like PreparedStatements in jdbc. Is it possible to do this with hibernate?
Note: the code above has been simplified by removing unrelated parts, the method, variable and class name are made fictious for privacy reason. If you find a typo, please let me know in the comment section since I typed the code manually.
In Hibernate/JPA you can write queries with Hibernate Query Language/ JPA query language and create NamedQueries. NamedQuery is compiled when server is started so you can consider it like some kind of prepared statement.
You can try to write HQL query which can get all entity instances with single query.
I will give you example in JPQL but you can write it with HQL as well.
#NamedQueries({
#NamedQuery(name = "QUERY_BY_ID",
query = "SELECT u from SomeEntity se WHERE se.id IN (:idList)"),
})
class SomeEntity {
}
class SomeEntityDao {
public List<SomeEntity> findIdList(List<Long> idList) {
Query query = entityManager.createNamedQuery("QUERY_BY_ID");
query.setParameter("idList", idList);
return query.getResultList();
}
}
I found the best solution. I put the query that select columns from table Account and Client joined together into a View (VIEW_ACCOUNT_CLIENT), then I made entity class (AccountClientView) for the view and fetch it using hibernate, the result is wow, it boosts the performance drastically. Using the real code, it could takes about 15-20 minutes to finish the loop, but using View, it only takes 8-10 seconds
#Entity
#Table(name = "VIEW_ACCOUNT_CLIENT")
public class AccountClientView implements Serializable {
...
}
It's not clear what you want to achieve. I wouldn't do service calls in a loop. Why don't you use a NamedQuery?
Retrieve all Clients attached to the given Accounts, then iterate over that list of Clients.
SELECT c from Client c JOIN c.account a WHERE a.id IN (:accounIds)
But it really depends on the business requirement!
Also it's not clear to me why don't you just call:
Client client = account.getClient();
You might want to load your accountList with the clients already fetched in. Either use eager fetching, or fetch join. If the Account entity does not contain a Client, you should have a very good reason for it.
im having javax.jdo.JDODetachedFieldAccessException when i want to, after retrieve all of my Entites as a List in my DAO implementation, ask for one atrribute object from my Entity.
public List<T> findAll() {
this.entityManager = SingletonEntityManagerFactory.get().createEntityManager();
EntityTransaction tx = this.entityManager.getTransaction();
try {
tx.begin();
return this.entityManager.createQuery(
"select f from " + clazz.getName() + " as f").getResultList();
}finally {
tx.commit();
if (tx.isActive()) {
tx.rollback();
}
this.entityManager.close();
}
}
for instance, supposing T has a property of class A that is already an Entity persisted, i can't get A after having List
But i don't have this problem if I only look for a single Entity by Id. I obtain my entity and I can ask without problems for its attribute objects already persisted
public T getById(final Key id) {
return getEntityManager().find(clazz, id);
}
now i can do
A a= t.getA();
How can I write my implementation of findAll() avoiding this error? maybe another Component instead of EntityManager? How can i make it generic, and not having to implement specific code for specific type of entities?
What you do there doesn't make sure the field is loaded before leaving that method, so either access it, or make sure it is fetched by default.