Spring Data, JPA #ManyToOne lazy initialization not working - java

I know there are many similar questions about this trouble but nothing works for me.
I have #ManyToOne relationship between Aim and User.
#ManyToOne(fetch = FetchType.LAZY, optional = false)
#JoinColumn(name = "user_id", nullable = false, updatable = false)
private User user;
and
#OneToMany(fetch = FetchType.LAZY, mappedBy = "user")
private Collection<Aim> userAims;
respectively.
#Override
#Transactional(propagation = Propagation.REQUIRED)
#PreAuthorize("isAuthenticated() and principal.user.isEnabled() == true")
public Aim findById(String aimId) throws NumberFormatException, EntityNotFoundException {
Aim aim = null;
try {
aim = aimRepository.findOne(Long.parseLong(aimId));
} catch (NumberFormatException e) {
throw new InvalidDataAccessApiUsageException(e.getMessage(), e);
}
if (aim == null) throw new EntityNotFoundException("Aim with id: " + aimId + " not found!");
return aim;
}
#OneToMany associations work fine with lazy fetching. Method isn't nested to another #Transactional method so #Transactional works fine.
So the record exists.
Classes User and Aim aren't final and implement
Serializable
Some sources advice to put annotations on getters. It also doesn't
work.
#Fetch(FetchMode.SELECT) the same situation =\
Query via Hibernate results the same, but HQL query with left join
fetch works fine
My FK is ON UPDATE CASCADE ON INSERT CASCADE
optional = false also tried...
Pay attention that I haven't the LazyInitException
Thanks in advance!

I'm guessing from the code in your findById method, and by the reference to "lazy initialization not working" in the title, that you are wanting to find an Aim object by it's numeric Id, along with the associated User object.
In order to do this with lazy-loading, you need to 'get' the associated object, and (most importantly) you need to 'get' one of the associated entity's fields.
So the code inside the try block should be:
aim = aimRepository.findOne(Long.parseLong(aimId));
if (aim != null && aim.getUser() != null) {
aim.getUser().getUserId(); // doesn't need to be assigned to anything
}
Alternatively, if you have a logger available you can use the userId in a debug or trace log message:
if (aim != null && aim.getUser() != null) {
logger.debug("Lazy-loaded User " + aim.getUser().getUserId());
}
This has the added benefit that you can debug how things are lazy-loaded.
By the way, we found out the hard way that making a find routine throw an Exception when it doesn't find something is a bad idea. This is because you might want to use the find routine to find out if an Entity does NOT exist. If that is happening within a transaction, your exception may trigger an unwanted rollback (unless you specifically ignore it). Better to return null and check for that instead of using a try ... catch.

Related

Unique constraint violated on replace item from list in test

So I have client = creditor which has list of documents. This list can contain only one type of each document, so i have method add document which adds new documnet, but if there is already document of this type it should be replaced.
this test fail on unique constraint
def "should replace documents with same type"() {
given:
def creditor = creditors.create(CreditorHelper.createSampleCreditorForm())
def documentType = DocumentTypeEvent.INVESTMENT_INSTRUCTION
and:
def old = documents.addDocument(new DocumentForm("urlOld", creditor.creditorReference, documentType, ZonedDateTime.now()))
when:
documents.addDocument(new DocumentForm("urlNew", creditor.creditorReference, documentType, ZonedDateTime.now()))
then:
def newResult = documentRepository.findByCreditorReference(creditor.creditorReference)
newResult.size() == 1
newResult.find {
it.url == "urlNew"
}
and:
documentRepository.findByHash(old.hash) == Optional.empty()
}
implementaion is simple replace:
#Transactional
public Document addDocument(final DocumentForm documentForm) {
return creditorRepository.findByCreditorReferenceIgnoreCase(documentForm.getCreditorReference())
.addDocument(new Document(documentForm));
}
above calls:
public Document addDocument(Document newDocument) {
documents.removeIf(existingDocument -> existingDocument.getType() == newDocument.getType());
documents.add(newDocument);
}
entity:
#OneToMany(cascade = CascadeType.ALL, orphanRemoval = true)
#JoinColumn(name = "creditor_id")
#Builder.Default
private List<Document> documents = new ArrayList<>();
funny is that when I remove unique constraint from flyway test is passing, so it seems like problems with transaction.
I think it might be related to Hibernate's queries ordering during flush time. Because persisting new entities is invoked as first operation by Hibernate's session, you get exception as entity is present in DB during flush time. Turn on show_sql option in Hibernate and try look at logs what is the real order of queries sent to DB.
Also read Vlad's post about ordering: A beginner’s guide to Hibernate flush operation order. You can read code of class EventListenerRegistryImpl as well and see how ordering looks like.

Hibernate - Many To Many wants persist data again

I have User class, where I want ManyToMany relation referencing on itself - particalury I am solving that user could follows/be followed another user. There is no need for other attributes, so it should be one table with composite primary key. This is how it is annotated:
#ManyToMany(cascade = {CascadeType.ALL, CascadeType.MERGE})
#JoinTable(name = "user_followers", joinColumns = { #JoinColumn(name = "follower") }, inverseJoinColumns = { #JoinColumn(name = "following") })
private Set<User> following = new HashSet<User>();
#ManyToMany(mappedBy = "following")
private Set<User> followers = new HashSet<User>();
Everything looks fine - table is generated, PK is composite follower_following. When I persist data on first try, it works - insert statement is executed. BUT on the second time, it is still trying to insert previously executed relation (obviously not the same relation) and I get:
Unexpected RuntimeException
Last cause: Duplicate entry 'follower-following' for key 'PRIMARY'
// EDIT: There are correct FK keys instead of follower-following
This is how I add follower and persist user:
if (!user.following.contains(toFollowUser)) {
user.following.add(toFollowUser);
}
if (session.contains(user)) {
session.persist(user);
} else {
session.merge(user)
}
session.getTransaction().commit(); // HERE I get exception
From my point of view, it looks like Hibernate doesn't see that previous relation was peristed and written to database and still try to insert new record that of course falls on duplicated key. Do you have an idea what am I doing wrong?

Hibernate save returns wrong generated id

I am having trouble using Hibernate with MSSQL Server 2012. No matter what I do when I try to insert a value in a certain table using Hibernate I get generated id=0.
Here is the model.
#Entity
#Table(name = "tbl_ClientInfo")
public class ClientInfo {
#Id
#GeneratedValue(strategy=GenerationType.IDENTITY)
#Column (name = "auto_Client_ID", unique=true, nullable=false)
private int auto_Client_ID;
...
Here is the write.
public boolean addNewClient(Client client) {
// there is a class that wraps SessionFactory as singleton
Session session = getSessionFactory().openSession();
Transaction tx = null;
Integer clientFamId; //client family info id
Integer clientId; // actual client id
try {
// create fam info first with some data - need id for ClientInfo
tx = session.beginTransaction();
ClientFam clientFam = new ClientFam();
clientFamId = (Integer) session.save(clientFam);
clientFamId = (Integer) session.getIdentifier(clientFam); // this returns the right id
session.flush();
ClientInfo clientInfo = new ClientInfo();
clientInfo.setABunchOfFields(withStuff); //multiple methods
session.save(clientInfo);
clientInfoId = (Integer) session.getIdentifier(clientInfo); // this is always 0
session.flush();
tx.commit();
} catch (HibernateException e) {
if (tx!=null) tx.rollback();
e.printStackTrace();
return false;
} finally {
session.close();
}
return true;
}
In the database the PK auto_Client_ID is clustered, set to IDENTITY(1,1). Both ClientInfo and ClientFam records are created in the db, but hibernate returns 0. I also tried catching the value from save, but it's also 0.
I don't want to commit in-between separate insert: the transaction is when all inserts are fine (there are more after this, but I can't get to them because of this id issue yet).
The model for ClientFam is almost the same: the id field is #GeneratedValue(strategy=GenerationType.IDENTITY) as well.
I also tried specifying this for ClientInfo
#GeneratedValue(generator="increment", strategy=GenerationType.IDENTITY)
#GenericGenerator(name = "increment", strategy = "increment")
The first time I ran it it returned the correct value. However, the second time I ran it I got an error:
Cannot insert explicit value for identity column in table 'Report' when IDENTITY_INSERT is set to OFF
And that was the end of trying that. Everywhere I looked the recommendation is to use GenerationType.IDENTITY for auto incremented field in the db. That's supposed to return the right values. What might I be doing wrong?
I also tried getting the id from the ClientInfo object itself (I thought it should get written into it) after the right, but it's was also 0. Makes me think something is wrong with my ClientInfo model and/or annotations in it.
I found the problem with my situation - has nothing to do with Hibernate. There is a instead of insert trigger that wasn't returning id and hence messing up what save() returns.
This is just an educated guess, but you might want to remove the "unique=true" clause from the #Column definition. Hibernate may be handling the column as a unique constraint as opposed to a primary key.

Removing entities from Collection in hibernate

I'm trying to manually delete every entity that's in a collection on an entity. The problem is, the entities don't get deleted from the database, even though they get removed from the collection on the task.
Below is the code im using to achieve this:
public int removeExistingCosts(final DataStoreTask task) {
int removedAccumulator = 0;
Query query = entityManager.createNamedQuery(DataStoreCost.GET_COSTS_FOR_TASK);
query.setParameter(DataStoreCost.TASK_VARIABLE_NAME, task);
try {
List costsForTask = query.getResultList();
for(Object cost : costsForTask) {
task.getCosts().remove(cost);
removedAccumulator++;
}
} catch (NoResultException e) {
logger.debug("Couldn't costs for task: {}", task.getId());
}
entityManager.flush();
entityManager.persist(task);
return removedAccumulator;
}
Any ideas?
P.S the collection is represented as:
#OneToMany(targetEntity = DataStoreCost.class, mappedBy = "task", cascade = CascadeType.ALL)
private Collection<DataStoreCost> costs;
Cheers.
I think you need to explicitly remove the Cost entity via the entityManager. When you remove the Cost from the Tasks cost list you actually only remove the reference to that instance. It does not know that that particular Cost will not be used anywhere else.
It's not deleting the entity, because it doesn't know if something else is referring to it.
You need to enable delete orphan. In jpa2, use the orphanRemoval attribute. If you're using hibernate annotations, use CascadeStyle delete orphan.

How to eagerly load lazy fields with JPA 2.0?

I have an entity class that has a lazy field like this:
#Entity
public Movie implements Serializable {
...
#Basic(fetch = FetchType.LAZY)
private String story;
...
}
The story field should normally be loaded lazily because it's usually large. However sometimes, I need to load it eagerly, but I don't write something ugly like movie.getStory() to force the loading. For lazy relationship I know a fetch join can force a eager loading, but it doesn't work for lazy field. How do I write a query to eagerly load the story field?
I'd try Hibernate.initialize(movie). But calling the getter (and adding a comment that this forces initialization) is not that wrong.
The one possible solution is:
SELECT movie
FROM Movie movie LEFT JOIN FETCH movie.referencedEntities
WHERE...
Other could be to use #Transactional on method in ManagedBean or Stateless and try to access movie.getReferencedEntities().size() to load it but it will generate N+1 problem i.e. generating additional N queries for each relationship which isn't too efficient in many cases.
You can use the fetch all properties keywords in your query:
SELECT movie
FROM Movie movie FETCH ALL PROPERTIES
WHERE ...
To quote the JPA spec (2.0, 11.1.6):
The LAZY strategy is a hint to the persistence provider runtime that data should be fetched
lazily when it is first accessed. The implementation is permitted to eagerly fetch data for which the
LAZY strategy hint has been specified.
Hibernate only supports what you are trying if you use its bytecode enhancement features. There are a few ways to do that. First is to use the build-time enhancement tool. The second is to use (class-)load-time enhancement. In Java EE environments you can enable that on Hibernate JPA using the 'hibernate.ejb.use_class_enhancer' setting (set it to true, false is the default). In Java SE environments, you need to enhance the classes as they are loaded, either on your own or you can leverage org.hibernate.bytecode.spi.InstrumentedClassLoader
If you don't mind having a POJO as a query result you can use constructor query. This will require your object to have constructor with all needed parameters and a query like this:
select new Movie(m.id, m.story) from Movie m
I would suggest to traverse the objects using Java reflection calling all methods starting with "get" and repeat this for all the gotten object, if it has an #Entity annotation.
Not the most beautiful way, but it must be a robust workaround. Something like that (not tested yet):
public static <T> void deepDetach(EntityManager emanager, T entity) {
IdentityHashMap<Object, Object> detached = new IdentityHashMap<Object, Object>();
try {
deepDetach(emanager, entity, detached);
} catch (IllegalAccessException e) {
throw new RuntimeException("Error deep detaching entity [" + entity + "].", e);
} catch (InvocationTargetException e) {
throw new RuntimeException("Error deep detaching entity [" + entity + "].", e);
}
}
private static <T> void deepDetach(EntityManager emanager, T entity, IdentityHashMap<Object, Object> detached) throws IllegalAccessException, InvocationTargetException {
if (entity == null || detached.containsKey(entity)) {
return;
}
Class<?> clazz = entity.getClass();
Entity entityAnnotation = clazz.getAnnotation(Entity.class);
if (entityAnnotation == null) {
return; // Not an entity. No need to detach.
}
emanager.detach(entity);
detached.put(entity, null); // value doesn't matter. Using a map, because there is no IdentitySet.
Method[] methods = clazz.getMethods();
for (Method m : methods) {
String name = m.getName();
if (m.getParameterTypes().length == 0) {
if (name.length() > 3 && name.startsWith("get") && Character.isUpperCase(name.charAt(3))) {
Object res = m.invoke(entity, new Object[0]);
deepDetach(emanager, res, detached);
}
// It is actually not needed for searching for lazy instances, but it will load
// this instance, if it was represented by a proxy
if (name.length() > 2 && name.startsWith("is") && Character.isUpperCase(name.charAt(2))) {
Object res = m.invoke(entity, new Object[0]);
deepDetach(emanager, res, detached);
}
}
}
}

Categories

Resources