I'm trying to make Spring JPA Data work for me, but have been struggling. Here is the problem.
I have two domain classes with a simple OneToMany relation between them:
class Card {
#ManyToOne(mappedBy="user")
private User user;
}
class User {
#OneToMany
private List<Card> cards;
}
I have set up Repository interface for each of the class: CardRepository, UserRepository extending the JpaRepository, both repository is injected into a service
#Service
#Transactional(readOnly = true)
class Service {
#Autowired
CardRepository repo1;
#Autowired
UserRepository repo2;
public void someMethod() {
// make use of the repos
User u=repo2.findByIdentifier("ID1");
List<Card> cards = u.getCards();
//do something with the cards will throw lazyinitialization exception.
}
}
pretty basic setting up.
problem comes with the someMethod(), in which I queried an User with its identifier, then try to get the mapped #OneToMany's list, then the LazyInitialization exception happened.
I'm not quite sure if I missed something there? seems as long as the repository's method is returned, the entitymanager is closed; If that's the case, i'm wondering how can I get the relationship without define another repository method?
If I however set the #OneToMany's fetch to be eager, no problem, but it is something I really don't want to do.
The delete() of the repository seems also problematic. If I delete a card first, then trying to delete() its owning user (which still have the card in its list), the delete will fail complaining cannot find the card. but I didn't set any removal propagation from the User to the Card!
I hope someone can explain how the entitymanager is used in the JpaRepository, it seems making Jpa programming more harder. I know all the repository is automatically generated but if someone can point to how they are implemented that wil be very helpful.
Thanks.
Wudong
First of all, the mapping tag needs to be interchanged, i.e.
class User(){
#OneToMany(mappedBy="user")
private List<Card> cards;
}
class Card{
#ManyToOne
private User user;
}
and how are you calling your someMethod ? if it is called in init-method, you'll not be able to use #Transactional
Related
I have just come upon something that I can't describe in any other way than bizarre.
I have a service that is supposed to do this:
it gets passed an external identifier of a customer
it looks up the customer's internal ID
then loads and returns the customer
I'm using optionals as there is a potential chance that external identifiers can't be resolved.
#Transactional(readOnly = true)
public Optional<Customer> getCustomerByExternalReference(String externalId, ReferenceContext referenceContext) {
return externalIdMappingService.resolve(externalId, referenceContext, InternalEntityType.CUSTOMER)
.map(x->new CustomerId(x.getTarget()))
.map(customerRepository::getById);
}
what's noteworthy is here is that: externalIdMappingRepository.resolve returns an Optional<ExternalIdReference> object. If that is present, I attempt to map it to a customer that I then look up from the database. customerRepository is a regular spring data JPA repository (source code below)
However, when trying to access properties from Customer outside the service, I get an exception like this:
org.hibernate.LazyInitializationException: could not initialize proxy [Customer#Customer$CustomerId#3e] - no Session
at org.hibernate.proxy.AbstractLazyInitializer.initialize(AbstractLazyInitializer.java:176)
at org.hibernate.proxy.AbstractLazyInitializer.getImplementation(AbstractLazyInitializer.java:322)
at org.hibernate.proxy.pojo.bytebuddy.ByteBuddyInterceptor.intercept(ByteBuddyInterceptor.java:45)
at org.hibernate.proxy.ProxyConfiguration$InterceptorDispatcher.intercept(ProxyConfiguration.java:95)
at Customer$HibernateProxy$R0X59vMR.getIdName(Unknown Source)
at CustomerApiModel.<init>(CustomerApiModel.java:27)
I understand that this means, that Hibernate decided to lazy load that entity. Once outside the transactional boundaries of the service, it's not able to load the data for that object anymore.
My Question is: Why does Hibernate/Spring Data try a lazy fetching strategy when I essentially just load a specific object by ID from a Spring Data Repository and how I can disable this behaviour the right way.
I'm aware that there is a couple of workarounds to fix the problem (such as allowing hibernate to open sessions at will, or to access properties of that object inside the service). I'm not after such fixes. I want to understand the issue and want to ensure that lazy fetching only happens when it's supposed to happen
Here's the code for customer (just the part that I think is helpful)
#Entity
#Table(name="customer")
#Getter
public class Customer {
#EmbeddedId
private CustomerId id;
#Embeddable
#NoArgsConstructor
#AllArgsConstructor
#EqualsAndHashCode
public static class CustomerId implements Serializable {
private long id;
public long asLong() {
return id;
}
}
}
and here's the source code of the repository:
public interface CustomerRepository extends Repository<Customer, CustomerId> {
List<Customer> findAll();
Customer getById(CustomerId id);
Optional<Customer> findOneById(CustomerId id);
Optional<Customer> findOneByIdName(String idName);
}
By declaring the method Customer getById(CustomerId id); in your CustomerRepository interface, you chose to let your repostory selectively expose the corresponding method with the same signature from the standard spring-data repository methods, as explained by the Repository java doc:
Domain repositories extending this interface can selectively expose CRUD methods by simply declaring methods of the same signature as those declared in CrudRepository.
Different to what the doc says, this also includes methods from JpaRepository.
In the case of Customer getById(CustomerId id);, you therefore invoke the JpaRepository method with the same signature: T getOne(ID id);, which only invokes EntityManager#getReference , as suggested by it's doc:
[...] Returns a reference to the entity with the given identifier. Depending on how the JPA persistence provider is implemented this is very likely to always return an instance and throw an {#link javax.persistence.EntityNotFoundException} on first access. Some of them will reject invalid identifiers immediately. [...]
#see EntityManager#getReference(Class, Object) for details on when an exception is thrown.
When calling EntityManager#getReference, Hibernate first returns a non-initialized proxy of the Entity without executing any SQL statement at all, which is why your method only returns the non-initialized entity.
To fix this, you could change your service logic as follows:
#Transactional(readOnly = true)
public Optional<Customer> getCustomerByExternalReference(String externalId, ReferenceContext referenceContext) {
return externalIdMappingService.resolve(externalId, referenceContext, InternalEntityType.CUSTOMER)
.map(x->new CustomerId(x.getTarget()))
.map(id -> customerRepository.findOneById(id).get()); // <-- changed call
}
This way, spring-data would invoke CrudRepository#findById, which would internally call EntityManager#find and therefore return an initialized entity (or an empty Optional if none was found in the DB).
Related:
When use getOne and findOne methods Spring Data JPA
Why "findById()" returns proxy after calling getOne() on same entity? (attention when using getOne and findById in the same transaction)
I have a ManyToMany relationship between User and Role. I have a custom hibernate validation constraint on my roles Set in User.
In a #PostConstruct I save the initial roles (ADMIN, USER) to the database using standard JpaRepository from spring-data-jpa. I then create an initial user using the admin role.
If I do not have my custom validation, the association is saved correctly and I see an entry in user_role join table. If I have the validation, the user is inserted into the user table, but without an entry into user_role table. The returned entity has the role in the roles set, but it is not saved into the DB. The code is summarized below. I cannot understand how using the RoleRepo to fetch all of the roles could in any way break the save, but it does.
class User {
#Id
String username;
#ValidOption
#ManyToMany(cascade = {CascadeType.ALL //for example}, fetch=FetchType.EAGER)
Set<Role> roles;
}
class Role {
#Id
String name;
}
class CustomValidator implements ConstraintValidator<ValidOption, Object> {
RoleRepository roleRepo; //injected by spring... have spring factory
#Override
public boolean isValid(Object value, ConstraintValidatorContext context){
roleRepo.findAll() //<-------------- THIS CALL BREAKS THE SAVE
return true;
}
}
#Component
class UserCreator {
RoleRepository roleRepo;
UserRepo userRepo;
#PostConstruct
void setup(){
Role admin = roleRepo.saveAndFlush(new Role('ADMIN'));
roleRepo.saveAndFlush(new Role('USER'));
User user = new User('admin', Collections.singleton(admin));
userRepo.save(user); //<------ DOES NOT INSERT ADMIN INTO USER_ROLE JOIN TABLE
}
}
This works 100% exactly the way I would expect if I remove the custom validator. It may also work if I don't run this in PostConstruct and schedule it in a different thread, I need to check that.
Project with reproducible failing test case: https://github.com/tjhelmuth/SPR-22533/blob/master/src/test/java/spr22533/bug/BugExample.java
Accessing the EntityManager during validation is not guaranteed to work during validation.
Validation happens in "lifecycle callback methods".
For these the following restriction applies (Java Persistence Specification 2.2; Section 3.5.2 Lifecycle Callback Methods):
In general, the lifecycle method of a portable application should not invoke EntityManager or query operations, access other entity instances, or modify relationships within the same persistence context. A lifecycle callback method may modify the non-relationship state of the entity on which it is invoked.
To make it work, use a separate EntityManager, which of course might suffer from seeing a different set of changes since it runs a different transaction.
See also: Correct way to do an EntityManager query during Hibernate Validation
I've been using spring data rest without any problem but now I have a requirement that when a user performs a DELETE operation on a given entity i.e. DELETE /accounts/<id> I need to set a flag on the database marking that entity as deleted but i do want to keep the record.
Basically this means that I need to do an UPDATE instead of a DELETE operation in the database. I don't find any way to override the spring behavior for the delete(ID) method.
Some code:
#Entity
#Table(name = "account")
public class Account {
/*
Default value for this field is false but when a receive a
DELETE request for this entity i want to turn this flag
to false instead of deleting the record.
*/
#Column(name = "deleted")
private boolean deleted;
...
}
Account Repository
#RepositoryRestResource
public interface AccountRepository extends JpaRepository<Account, Integer> {
}
Any ideas?
Try to create a custom repository, to see how it would play out
http://docs.spring.io/spring-data/jpa/docs/1.9.0.RELEASE/reference/html/#repositories.custom-implementations
But delete is not the only place you'll need to change your logic.
I see 2 ways to handle the flag requirement:
Have an extra flag in your entity definition, and update it on Delete.
In this case you need to be careful, and rewrite all existing queries, to be sure, that removed entities would not be returned, and keep in mind this separation of results, for all future entities. (Although you can hack SpringData on low level, and append this flag automatically).
Delete entity from original collection and add it to another collection, where entities are stored before complete disposal.
In this case you'll need to have additional logic for managing disposal collections, but this has no implications on query logic. You can integrate with your existing application, by adding entity listener to your JPA definition (http://docs.spring.io/spring-data/jpa/docs/1.9.0.RELEASE/reference/html/#jpa.auditing)
It's enough that you override delete method of your #RepositoryRestResource, like so:
#RepositoryRestResource
public interface ProductRepository extends PagingAndSortingRepository<Product, Long> {
#Modifying
#Query("update Product p set deleted = true where p = :p")
void delete(Product p);
#Query("select p FROM Product p WHERE p.deleted = false")
Page<Product> findAll(Pageable pageable);
}
I think first you should use an interface to identify only the entities that will use the soft delete. Afterwards you can override the delete method. If the entity is instance of that interface set the deleted flag to true and call update else call the super implementation. Use SimpleJpaRepository instead of JpaRepository. Example for interfaces https://github.com/danjee/hibernate-mappings you can find here (Persistent and DefaultPersistent)
#Autowired
private AccountRepository accountRepository;
#Override
public void accountSoftDelete (Long id) {
Optional<Account> account1= accountRepository.findById(id);
account1.get().setDeleted(true);
accountRepository.save(account1.get());
I am writing an application that has typical two entities: User and UserGroup. The latter may contain one or more instances of the former. I have following (more/less) mapping for that:
User:
public class User {
#Id
#GeneratedValue
private long id;
#ManyToOne(cascade = {CascadeType.MERGE})
#JoinColumn(name="GROUP_ID")
private UserGroup group;
public UserGroup getGroup() {
return group;
}
public void setGroup(UserGroup group) {
this.group = group;
}
}
User group:
public class UserGroup {
#Id
#GeneratedValue
private long id;
#OneToMany(mappedBy="group", cascade = {CascadeType.REMOVE}, targetEntity = User.class)
private Set<User> users;
public void setUsers(Set<User> users) {
this.users = users;
}
}
Now I have a separate DAO class for each of these entities (UserDao and UserGroupDao). All my DAOs have EntityManager injected using #PersistenceContext annotation, like this:
#Transactional
public class SomeDao<T> {
private Class<T> persistentClass;
#PersistenceContext
private EntityManager em;
public T findById(long id) {
return em.find(persistentClass, id);
}
public void save(T entity) {
em.persist(entity);
}
}
With this layout I want to create a new user and assign it to existing user group. I do it like this:
UserGroup ug = userGroupDao.findById(1);
User u = new User();
u.setName("john");
u.setGroup(ug);
userDao.save(u);
Unfortunately I get following exception:
object references an unsaved transient instance - save the transient
instance before flushing: x.y.z.model.User.group ->
x.y.z.model.UserGroup
I investigated it and I think it happens becasue each DAO instance has different entityManager assigned (I checked that - the references in each DAO to entity manager are different) and for user entityManager does not manager the passed UserGroup instance.
I've tried to merge the user group assigned to user into UserDAO's entity manager. There are two problems with that:
It still doesn't work - the entity manager wants to overwrite the existing UserGroup and it gets exception (obviously)
even if it worked I would end up writing merge code for each related entity
Described case works when both find and persist are made using the same entity manager. This points to a question(s):
Is my design broken? I think it is pretty similar to recommended in this answer. Should there be single EntityManager for all DAOs (the web claims otherwise)?
Or should the group assignment be done inside the DAO? in this case I would end up writing a lot of code in the DAOs
Should I get rid of DAOs? If yes, how to handle data access nicely?
any other solution?
I am using Spring as container and Hibernate as JPA implementation.
Different instances of EntityManager are normal in Spring. It creates proxies that dynamically use the entity manager that is currently in a transaction if one exists. Otherwise, a new one will be created.
The problem is that your transactions are too short. Retrieving your user group executes in a transaction (because the findById method is implicitly #Transactional ). But then the transaction commits and the group is detached. When you save the new user, it will create a new transaction which fails because the user references a detached entity.
The way to solve this (and to do such things in general) is to create a method that does the whole operation in a single transaction. Just create that method in a service class (any Spring-managed component will work) and annotate it with #Transactional as well.
I don't know Spring, but the JPA issue is that you are persisting a User that has a reference to a UserGroup, but JPA thinks the UserGroup is transient.
transient is one of the life-cycle states a JPA entity can be in. It means it's just created with the new operator, but has not been persisted yet (does not have a persistent identity yet).
Since you obtain your UserGroup instance via a DAO, it seems like something is wrong there. Your instance should not be transient, but detached. Can you print the Id of the UserGroup instance just after your received it from the DAO? And perhaps also show the findById implementation?
You don't have cascade persist on the group relation, so this normally should just work if the entity was indeed detached. Without a new entity, JPA simply has no way to set the FK correctly, since it would need the Id of the UserGroup instance here but that (seemingly) doesn't exist.
A merge should also not "overwrite" your detached entity. What is the exception that you're getting here?
I only partially agree with the answers being given by the others here about having to put everything in one transaction. Yes, this indeed may be more convenient as the UserGroup instance will still be 'attached', but it should not be -necessary-. JPA is perfectly capable of persisting new entities with references to either other new entities or existing (detached) entities that were obtained in another transaction. See e.g. JPA cascade persist and references to detached entities throws PersistentObjectException. Why?
I am not sure how but I've managed to solve this. The user group I was trying to assign the user to had NULL version field in database (the field annotated with #Version). I figured out it was an issue when I was testing GWT RequestFactory that was using this table. When I set the field to 1 everything started to work (no changes in transaction handling were needed).
If the NULL version field really caused the problem then this would be one of the most misleading exception messages I have ever got.
Currently I have a child entity that has a #ManyToOne association to it's parent entity. Previous developers have set this field as lazy="false" to get the parent whenever needed when the session is closed too, however I decided it should be lazy="true" as it's not always used but when doing so I ran into LazyInitializationException because the session is closed and the child is detached from the session when it tries to get the parent.
I was wondering if it's right to move some more logic of the run method as seen bellow to the service class which interacts with DAOs thus I could avoid the exception because currently the service classes are like plain classes which have the needed DAOs injected and they just call the DAO method and returns the result.
Should I put like more methods in the service class which interact with the entities, which would get the user and check everything for log in action, get parent if needed and then just return the log in result to run method..
public class Login extends Runnable {
private UserService userService;
...
public void run() {
...
User user = userSerivce.getById(id);
Account account = user.getAccount(); //LazyInitializationException
...
if (account.isLocked()) {
...
}
...
userService.save(user);
//Send some message to the user..
}
}
public class UserServiceImpl implements UserService {
private UserDAO userDAO;
...
public User getById(long id) {
return userDAO.getById(id);
}
public void save(User user) {
userDAO.save(user);
}
}
public UserDAOImpl implements UserDAO {
private SessionFactory factory;
...
public User getById(long id) {
return (User) factory.getCurrentSession().load(User.class, id);
}
public void save(User user) {
factory.getCurrentSession().saveOrUpdate(user);
}
}
I use Spring's <tx:advice> to handle the closing and other transaction related stuff.
I prefer to have all of my entity relationships as lazy since I don't know if and when I'll need those external entities. This way I can avoid unnecessary joins when I don't need the additional entities. If I do end up needing the entity I create a named query and eager fetch the entity or collection. Here's an example.
I do agree though that you should be sending a DTO instead of the entity back to your calling front end application. Hibernate entities are full of proxies and it would be inefficient to send them back. I'm not really sure if you are sending these objects to a jsp/velocity/etc file or to an external application but I would recommend using a DTO if you are sending back JSON or something similar to the calling application. Here's another question that deals with DTO's click here that discusses 2 frameworks for easy conversion.
Create DTOs, don't send JPA Entities over the net.
When you create the DTOs, you will have to access the required properties, what will trigger to load them.