I have a simple query that worked for a good time, now i changed some stuff in my code to:
(hibernate.cfg.xml)
<property name="hibernate.transaction.factory_class">org.hibernate.transaction.JDBCTransactionFactory</property>
<property name="hibernate.current_session_context_class">thread</property>
and the following code:
public UserEn findByIDFetch(Long id, String... fetches) {
beginTransaction();
DetachedCriteria dc = DetachedCriteria.forClass(UserEn.class);
for (String fetch : fetches) {
dc.createAlias(fetch, fetch, JoinType.INNER_JOIN);
}
dc.add(Restrictions.eq("id", id));
UserEn result = (UserEn) dc.getExecutableCriteria(getSession()).uniqueResult();
commitTransaction();
return result;
}
after the commit, result object don't have the list loaded (that is comming from fetches)
if i do result.getAnyList().size(); before the commit, it keeps loaded, also, if i use HQL it works perfectly. (but it's not how it's supposed to work (let it open and load when used), createAlias should work fine as always)
It looks like DetachedCriteria's createAlias is not fetching the given path (in this case is characterEnList)
#EDIT
I found out that if i use setFetchMode in the desired path (characterEnList in this case) the fetching works but if i use createAlias (as usual) it simply stops working, i don't know how, probably a hibernate bug or something, anyway, i'll wait for someone answer, maybe someone that had the same problem...
it is lazy loaded only when accessed during the transaction. So you must call the lazy loaded field during the transaction, for hibernate to fetch it. It will then be available in the view.
or of course make it eager.
Found as the same problem as follows: https://hibernate.atlassian.net/browse/HHH-7842
This is probably a bug, if i ask it to be inner join in the alias, it should be, i can do this with HQL, why can't i do with Criteria?
So, now i will have to use the LEFT_OUTER_JOIN type to solve this, or don't use alias at all (if i don't need to make a where clause or 2-depth fetch)
Hope it help someone, thanks for all your help!
example
dc.createAlias(fetch, fetch, JoinType.LEFT_OUTER_JOIN);
OR
dc.setFetchMode(fetch, FetchMode.JOIN);
Related
With Spring JPA is there an easy way to use native queries but maintaining database independence, for example by using the query which fits best?
At the moment I do this by checking the currently set Dialect from the Environment and call the proper method of my Repository:
public Foo fetchFoo() {
if (POSTGRES_DIALECT.equals(env.getRequiredProperty("hibernate.dialect"))) {
return repo.postgresOptimizedGetFoo();
}
return repo.getFoo();
}
This works but I have the feeling that there is a better way or that I am missing something. Especially because (Spring) JPA allows it to use native queries quite easily but that breaks one of its big advantages: database independence.
As per my understanding, this can be achieved simply by using #Transactional(readOnly=false) and then instead of calling session.createQuery, one can use session.createSQLQuery, as provided in this example.
Your sql can be any of your native query.
Hope this works for you. :)
#Override
#Transactional(readOnly = false)
public Long getSeqVal() {
Session session = entityManager.unwrap(Session.class);
String sql = "SELECT nextval('seqName')";
Query query = session.createSQLQuery(sql);
BigInteger big = (BigInteger) query.list().get(0);
return big.longValue();
}
This is just an idea: I do not know whether it works or not:
My idea would be having subinterfaces, one normal Spring-Data-JPA-interface with all methods for one entiy (without native query hints). Than I would crate a subinterface for every database, that "override" the database specific native statements. (This intrface would be empty if there are no DB specific statements). Then I would try configure Spring-JPA with some profiles to load the right specific interface (for example by a class-name or package-name-pattern)
This seems like a way to complicated way to get queries to work.
If you really want to use optimized queries make it at least transparant for your code. I suggest using named queries and create an orm.xml per database (much like Spring Boot uses to load the schema.xml for a different database).
In your code you can simply do
public interface YourRepository extends JpaRepository<YourEntity, Long> {
List<YourEntity> yourQueryMethod();
}
This will look for a named query with the name YourEntity.yourQueryMethod. Now in your orm.xml add the named query (the default one and in another one the optimized one).
Then you need to configure your LocalContainerEntityManagerFactory to load the specific one needed. Assuming you have a property defining which database you use, lets name it database.type you could do something like the following
<bean class="LocalContainerEntityManagerFactoryBean">
<property name="mappingResources" value="classpath:META-INF/orm-${database.type}.xml" />
... other config ...
</bean>
This way you can keep your code clean of the if/then/else construct and apply where needed. Cleans your code nicely imho.
I have 2 Entitites, one maps to a database table, the other to a database view.
The data of the view depends on the table.
#javax.persistence.Table(name = "BOOKING_INFO", schema = "BOOKING")
#Entity
public class BookingInfo extends AbstractBooking {
#javax.persistence.Table(name = "BOOKING_VIEW", schema = "BOOKING")
#Entity
#Immutable
public class BookingView extends AbstractBooking {
This works fine in most cases, however when we write (insert or update) the Booking entity and then do queries (in my case a count) on the BookingView entity, we get stale data.
Why this happens is clear to me (hibernate caching, it only flushes when it detects that a select needs some data flushed).
So if I would do a query on the Booking entity, it would trigger a flush.
I have found the #Synchronize Annotation in Hibernate which sounds like it should fix this problem, like this:
#javax.persistence.Table(name = "BOOKING_VIEW", schema = "BOOKING")
#Entity
#Immutable
#Synchronize("BOOKING.BOOKING_INFO")
public class BookingView extends AbstractBooking {
However this does not make any difference (flush only happens at the end of the transaction). Also the documentation I have found about this annotation is quite lacking and not very helpful.
EDIT: I also tried #Synchronize("BOOKING_INFO") (without the schema name, and also lowercase, but that made no difference)
The docs say that it is mostly used with #Subselect but it is not a must (I don't want that).
Has anyone ever successfully used this annotation?
Is there any other way to handle database views in Hibernate?
Am I missing something else?
Thanks to a colleague we were able to debug and fix this, the problem was that our Hibernate naming-strategy lowercased our table-names, so the correct annotaiton is:
#Synchronize("BOOKING.booking_info")
How to debug this:
set breakpoints in Hibernates ActionQueue class in the areTablesToBeUpdated methods.
There we saw that it compared "BOOKING.BOOKING_VIEW" to "BOOKING.booking_view".
We think this is a bug in hibernate because it should either apply the naming-strategies also to the values from #Synchronize or compare these case-insensitive (which could theoretically lead to too many flushes if you have a crazy database which uses tables with the same name only differentiated by casing).
Created a Hibernate issue: https://hibernate.atlassian.net/browse/HHH-10002
I have Document entity and some managed document object for doc with id=1.
Document managedDoc = entityManager.find(Document .class, 1);
managedDoc.setName("changedName");
As I know, managed doc state changed in persistent context (futher PC) after calling setter but nothing changed in database. Somewhere in my code I do the following:
Query query = entityManager.createQuery("from Document");
List<Document> list = query.getResultList();
return list;
When I perform select-all query as shown above, is document with id=1 taken from DB or from PC? From DB means select will not see new name because new name still in PC.
Actually, my problem is in updating via merge() and flush() and futher retrieving all objects - currently my select-all query doesn't see new values of some fields. Looks like merge+flush is OK, but JPA Query reads not from DB but from PC. But even if I'm right, both PC and DB contains new value of the name, why my select-all doesn't see it?
Moreover, select all sometimes returns correct/updated values, sometimes not
UPDATE
Clarification:
I put some object to PC via entityManager.find(Document .class, 1);
I create new detached instance with some name property setted. Id and other props gotten from managed instance. For example,
managedDoc = getFromSomeDataStructure();
Document nonManaged = new Document(managedDoc.getId()); nonManaged.setName("newName");
I update DB via em.merge(nonManaged);flush();
I saw my changes in DB when check it in Workbench.
I'm pressing F5 (and even CTRL+F5) button which performs select-all JPQL query and on each odd button press==select-all query I see non-actual old value, on each even button press==select-all query I see correct value.
It will be taken from the Persistent Context, as long as it has them their. To be more correct: as long as you have an entity in a managed state (i.e in the Persistence Context), it will not be overrriden. Of course, in the context when the same EntityManager instance is used.
If you want to refetch the value from DB, you have different possibilities:
Use another EntityManager, in a different transaction (important!).
Use EntityManager.detach() or if you want to clear the entire persistence context, use EntityManager.clear()
Use EntityManager.refresh() to throw out all changes made to an entity instance.
Let me try to clarify with a couple of examples an maybe this answer your question or with luck, helps to make the question clearer.
Scenario #1: Two Different Reads
Department department = em.find(Department.class, 1);
department.setName("Jedi Masters");
TypedQuery<Department> typedQuery = em.createQuery("SELECT d FROM Department d", Department.class);
List<Department> departments = typedQuery.getResultList();
for(Department found : departments){
if(found.getId().equals(1)){
assert found == department;
assert found.getName().equals(department.getName());
}
}
In this first scenario you can expect the department and found to be exact same instance and therefore have the exact same values. Both assertions above pass.
Scenario #2: Merging Detached Entity
//detached entity
Department department = new Department();
department.setId(1);
department.setName("Jedi Masters");
em.merge(department);
TypedQuery<Department> typedQuery = em.createQuery("SELECT d FROM Department d", Department.class);
List<Department> departments = typedQuery.getResultList();
for(Department found : departments){
if(found.getId().equals(1)){
assert found != department);
assert found.getName().equals(department.getName());
}
}
At least with Hibernate, the behavior in this case is slightly different. The two objects are not the same instance. They are different instances, but they still should have the same contents.
So, depending on your implementation on how you are comparing them you might get unexpected results, above all if you do not implemented a right equals/hashCode protocol for detached cases like this.
As answered here, I should call refresh() for each item in result list. But only refreshing didn't work for me. After setting READ COMMITED in persistence.xml by writing
<property name="hibernate.connection.isolation" value="2" />
everything worked perfectly.
P.S Don't forget to mark select method as #Transactional because refresh() doesn't work without this annotation.
Having trouble getting the following code to work...
I've got a JpaTransactionManager txManager autowired into this test. I know record with ID 39 does exist. It still exists at the end of the transactions, too...
TransactionStatus status = txManager.getTransaction(def);
A a = mock(A.class);
when(a.getId()).thenReturn(Long.valueOf(39));
sut.delete(a);
txManager.commit(status);
status = txManager.getTransaction(def);
a = sut.get(a.getId());
txManager.commit(status);
assertNull(a);
Code in class A:
public void delete(A a) {
a = getEntityManager().find(A.class, a.getId());
getEntityManager().remove(a);
}
Is there any reason the above assertNull check always fails? I cannot delete the object from my system no matter what I do - no error returned, and no issue with the delete reported. (As an aside, running a query directly in HQL does result in an update of the database...I just can't get it to work using the delete method supplied using JPA...)
Any assistance appreciated
You should take a look into these Hibernate classes/methods:
org/hibernate/engine/spi/ActionQueue.java executeActions(), unScheduleDeletion()
org/hibernate/event/internal/DefaultPersistEventListener.java onPersist()
I had the same problem - not being able to remove an entity. In my case, entityManager had two entities in its 'context': a parent with a list of children entities (cascade = CascadeType.ALL) and a child (from the list) to remove. So when I was trying to remove a child, parent still had a link to it, which was causing Hibernate to 'unScheduleDeletion' upon flushing.
So here is the solution:
Add orphanRemoval = true to the collection of children
Create method deleteChild(Child child) {child.setParent(null); children.remove(child);}
Use this method to delete children
Looks like another solution is to remove cascading, so that merging of parent entity wouldn't cause saving all its children. Not quite sure here (haven't checked).
Also, as far as I remember, JPA spec describes this situation.
My current project is done using JavaFX. I use properties to bind (bidirectionnal) view fields to bean (with BeanPathAdapter of JFXtras).
I choose to use JPA with ObjectDB as model.
This is the first time I use JPA in a standalone project and here I'm facing the problem of managed entities.
Actually, I bind managed entities to view fields and when the value of a view field changes, the entities is updated... and the database also.
I'm trying to find a way to manually persist/merge an entity so I can ask the user if he wants to save or not.
Here's the code i use to get list :
EntityManagerFactory emf = Persistence.createEntityManagerFactory("$objectdb/data/db.odb");
EntityManager em = emf.createEntityManager();
List<XXX> entities = em.createQuery("SELECT x FROM XXX x").getResultList();
So when i do
entity.setName("test");
the entity is updated in the database.
What i'm looking for is that the entity doesn't update automatically.
I tried (just after the getResultList)
em.clear();
or
em.detach(entity);
but it looses the relations instances even with CascadeType.DETACH.
I also tried
em.setFlushMode(FlushModeType.COMMIT);
but it still updates automatically...
I also tried to clone the object. But when i want to merge it, it gives me an exception :
Attempt to reuse an existing primary key value
I thought an alternative solution : use a variable as 'buffer' and fill the managed bean with buffer if the user saves. But BeanPathAdapter looses its sense. It's the same as filling view fields manually and filling bean fields manually after saving.
Could you help me to find a solution ?
Thanks,
Smoky
EDIT:
I answer to my own question :p
After 3 hours of research, I found a solution.
The 'cloning' solution was the 'best' of each I quoted but I don't think it's the best one.
The cause of the exception was the code I used to persist/merge my entity. I was persisting an entity non-managed with an already existing id. I thought I was merging...
I did a generic method not to fail again
public <T extends IEntity> T persist(T object) {
em.getTransaction().begin();
if (object.getId() == null) {
em.persist(object);
em.flush();
em.getTransaction().commit();
em.refresh(object);
}
else {
object = em.merge(object);
em.getTransaction().commit();
}
return object;
}
So the solution : When I have to bind the entity to the view, I use entity.clone() so I can use the entity as non-managed and merge when I want.
But if you have a proper solution, i'm interested :)
Thanks again
In addition to the solution above, standard solutions are:
Use detached objects in the model and then merge them into the EntityManager.
Use managed objects in the model, keeping the EntityManager open (with no detach/merge).