JPA #OneToMany by default is not so Lazy and fetch everything - spring

Current project runs on Spring + Openjpa + Roo. I have an entity like this
public class Zoo{
....
#OneToMany(mappedBy="zoo", fetch = FetchType.LAZY, cascade = CascadeType.REMOVE)
private List<Elephant> elephants;
#OneToMany(mappedBy="zoo", fetch = FetchType.LAZY, cascade = CascadeType.REMOVE)
private List<Jaguar> jaguars;
#OneToMany(mappedBy="zoo", fetch = FetchType.LAZY, cascade = CascadeType.REMOVE)
private List<Tiger> tigers;
....
}
Then I have a simple UI page just trying to update the Zoo name, however from SQL trace log after the simple query
SELECT t0.id, t0.name
FROM Zoo t0
WHERE t0.id = ?
there are a query like this
SELECT * FROM Zoo, Tiger, TigerProduct, TigerFood, FoodSupplier, SupplierContacts...
and a hundreds queries like this:
SELECT * FROM TigerProduct where tiger.id =: id_1
.....
SELECT * FROM TigerProduct where tiger.id =: id_n
....
....
SELECT * FROM TigerFood where tiger.id =: id_1
....
SELECT * FROM TigerFood where tiger.id =: id_n
And same to Jaguar and Elephant as well. This makes this simple action really slow when there is large amount of data resides in the database.
The java code for the first query and the ones after is pretty simple:
public static Zoo findZoo(Long id) {
if (id == null) return null;
return entityManager().find(Zoo.class, id);
}
from above it looks like the default FetchType.Lazy on #OneToMany relation is not so lazy at all that JPA tries to pull all data on the chain.
So what's going on and how to clear this situation? I only prefer to have the first query and that's it

FetchType.Lazy is only a hint, and not a requirement, as the documentation says. So you cannot rely on this behavior, you can only hope that your JPA provider respects your hint. Also JPA does not forces a way how the JPQL queries or entitymanager calls are converted to SQL code, so it is somehow our duty to select a JPA provider+version that knows how to do things better (as we define what better means). This was probably a decision that should encourage the competition between JPA providers.

Related

Spring's findByColumnName returning empty list

I need to retrieve a list of Category from the DB on the basis of value of column called owner. Here is my Category -
#Entity
#Table(name = "categories")
class Category(#Column(name = "category_id", nullable = false)
#Id #GeneratedValue(strategyGenerationType.AUTO)
var id: Long = 0,
#Column(name = "category_owner", nullable = false)
#field:NotNull(message = "Please assign an owner")
var owner: Long?,
#Column(name = "category_name", nullable = false)
#field:NotEmpty(message = "Please assign a name")
var name: String?)
Here is my interface which defines the function findByOwner -
interface CategoryRepository: JpaRepository<Category, Long> {
fun findByOwner(categoryOwner: Long): List<Category>
}
However, when I call the method, I get no response. I have made sure that the DB has correct data and I'm providing the correct owner Id. Have even invalidated the cache etc. What could be going wrong?
EDIT:
After spring.jpa.show-sql=true -
findAll()
Hibernate: select category0_.category_id as category1_0_, category0_.category_name as category2_0_, category0_.category_owner as category3_0_ from categories category0_
findByOwner()
Hibernate: select category0_.category_id as category1_0_, category0_.category_name as category2_0_, category0_.category_owner as category3_0_ from categories category0_ where category0_.category_owner=?
EDIT 2:
Turns out that my implementation was fine all along. The bug was in my service.
Create your named method according with the name of the column.
fun findByCategoryOwner(categoryOwner: Long): List<Category>
Or use #Query
#Query("SELECT * FROM categories WHERE category_owner = ?1", nativeQuery = true)
fun findByOwner(cateogryOwner: Long): List<Category
Can you put a breakpoint in org.springframework.data.jpa.repository.query.JpaQueryExecution class and when you execute findByOwner, it will come here.
When it reaches this breakpoint, select the query.createQuery(accessor).getResultList() and evaluate to see what value is returned by hibernate for spring-data-jpa to use
This post should help you. It appears to be happeing because of the parameter name mismatch.
Use camelCase to name your variables in Entity class then jpa will auto recognise the column name
findByCategoryOwner(String categoryOwner)
If you still wish to have underscore in your column names then try this
findByCategory_Owner(String categoryOwner)
I haven't tried the second option though
At least in java you need to provide the id in the method name:
**fun findByOwner_Id(categoryOwner: Long): List<Category>**
So change it from findByOwner -> findByOwnerId.

Spring Boot 2 with Hibernate Search, indexes are not created on save

I've an entity defined like below. If I use save() Hibernate does not create a new index for newly created entity. Updating/modifying an existing entity works well and as expected.
I'm using kotling with spring boot 2.
#Entity(name = "shipment")
#Indexed
data class Shipment(
#Id #GeneratedValue(strategy = GenerationType.IDENTITY) val id: Long = -1,
#JoinColumn(name = "user") #ManyToOne() var user: User?,
#IndexedEmbedded
#JoinColumn(name = "sender") #ManyToOne(cascade = [CascadeType.ALL]) val sender: Contact,
#IndexedEmbedded
#JoinColumn(name = "sender_information") #ManyToOne(cascade = [CascadeType.ALL]) val senderInformation: ShipmentInformation,
) {}
Save function, I'm using this same function to update my entity and index is updated if index exists.
#Transactional
fun save(user: User, shipment: Shipment): Shipment {
shipment.user = user;
return this.shipmentRepository.save(shipment)
}
application.properties
spring.jpa.properties.hibernate.search.default.directory_provider=filesystem
spring.jpa.properties.hibernate.search.default.indexBase=./lucene/
spring.jpa.open-in-view=false
If I restart the server, indexing manually works too.
#Transactional
override fun onApplicationEvent(event: ApplicationReadyEvent) {
val fullTextEntityManager: FullTextEntityManager = Search.getFullTextEntityManager(entityManager)
fullTextEntityManager.createIndexer().purgeAllOnStart(true)
fullTextEntityManager.createIndexer().optimizeAfterPurge(true)
fullTextEntityManager.createIndexer().batchSizeToLoadObjects(15)
fullTextEntityManager.createIndexer().cacheMode(CacheMode.IGNORE)
fullTextEntityManager.createIndexer().threadsToLoadObjects(2)
fullTextEntityManager.createIndexer().typesToIndexInParallel(2)
fullTextEntityManager.createIndexer().startAndWait()
return
}
I tried to force to use JPA transaction manager but It did not help me.
#Bean(name = arrayOf("transactionManager"))
#Primary
fun transactionManager(#Autowired entityManagerFactory: EntityManagerFactory): org.springframework.orm.jpa.JpaTransactionManager {
return JpaTransactionManager(entityManagerFactory)
}
Update
I think I found why I don't get the results of newly inserted entities.
My search query has a condition on "pid" field which is declared:
#Field(index = Index.YES, analyze = Analyze.NO, store = Store.NO)
#SortableField
#Column(name = "id", updatable = false, insertable = false)
#JsonIgnore
#NumericField val pid: Long,
and query:
query.must(queryBuilder.keyword().onField("customer.pid").matching(user.customer.id.toString()).createQuery())
pid is not stored and so newly inserted values are not visible. Can this be the cause?
BTW: How can I query/search by nested indexed document id? In my case it is customer.id which is DocumentId. I've tried to change the query like below but don't get any result, should I create a new field to query?
query.must(queryBuilder.keyword().onField("customer.id").matching(user.customer.id.toString()).createQuery())
Update 2
I found a solution and now getting the newly inserted datas too. There was an error with definition of "pid" field and I've defined my Fields as below and it works as expected.
#Fields(
Field(name = "pid", index = Index.YES, analyze = Analyze.YES, store = Store.NO)
)
#SortableField(forField = "pid")
#Id #GeneratedValue(strategy = GenerationType.IDENTITY) val id: Long?,
Can we search and sort by id in an easy way or is it the best practice? I know that we should use native JPA functions to get results by id but in my case I need to search by an embedded id to restrict search results. (depends on role of user) so therefore it is not an option for me.
And I don't understand why manual indexing works...
BTW: How can I query/search by nested indexed document id? In my case it is customer.id which is DocumentId. I've tried to change the query like below but don't get any result, should I create a new field to query?
Normally you don't need to create a separate field if all you want is to perform an exact match.
Can we search and sort by id in an easy way
Searching, yes, at least in Hibernate Search 5.
Sorting, no: you need a dedicated field.
or is it the best practice?
The best practice is to declare a field alongside your #DocumentId if you need anything more complex than an exact match on the ID.
I know that we should use native JPA functions to get results by id
I'm not sure I understand what you mean by "native JPA functions".
but in my case I need to search by an embedded id to restrict search results. (depends on role of user)
Yes, this should work. That is, it should work if the id is properly populated.
And I don't understand why manual indexing works...
Neither do I, but I suppose the explanation lies in the "error in the definition of "pid" field". Maybe the ID wasn't populated properly in some cases, leading to the entity being considered as deleted by Hibernate Search?
If you need me to give you a definitive answer, the best way to get it would be to create a reproducer. You can use this as a template: https://github.com/hibernate/hibernate-test-case-templates/tree/master/search
This looks odd:
#Id #GeneratedValue(strategy = GenerationType.IDENTITY) val id: Long = -1,
I'd expect a nullable long, initialized to null (or whatever is the Kotlin equivalent).
I'm not sure this is the problem, but I imagine it could be, as a non-null ID is generally only expected from an already persisted entity.
Other than that, I think you're on the right track: if mass indexing works but not automatic indexing, it may have something to do with your changes not being executed in database transactions.

JPA Property Expression - Match all children in OneToManyRelationship

Requirement
I have a simple OneToMany Relationship between two entities. I want to use JPA property expression to find Parent Entity with a condition which matches all children entities.
Parent
#Entity
public class PcSigningStatus {
#OneToMany(cascade = CascadeType.ALL, mappedBy = "signingStatus")
private List<PcSigningProcessEvent> signingProcessEvents = new ArrayList<>();
Child
#Entity
public class PcSigningProcessEvent {
#Enumerated(EnumType.STRING)
private ProcessEventType phase;
#ManyToOne
#JoinColumn(name = "SIGNING_STATUS_ID")
private PcSigningStatus signingStatus;
}
Parent Repository
public interface SigningStatusRepo extends CrudRepository<PcSigningStatus, Long> {
PcSigningStatus[] findBySigningProcessEvents_PhaseNot(ProcessEventType phase);
}
Issue
Suppose I only have 1 parent entity in database with 2 child entities containing different value of phase. The above query returns the same parent multiple times depending on the number of children it does not match the value of phase in. I need help in finding a way that Parent entity is returned only if the phase is not present in all the children entities
Maybe you should use #Query for this?
Hard to understand your question :) But you can try add distinct flag using Distinct mentioned in https://docs.spring.io/spring-data/jpa/docs/current/reference/html/#repositories.query-methods.query-creation
Edited: or you can use Query something like
SELECT * FROM PcSigningProcessEvent WHERE PcSigningProcessEvent.id NOT IN (SELECT SIGNING_STATUS_ID FROM PcSigningProcessEvent WHERE phase = ?)
So I found no way of doing this with property expression, and had to use Native Query instead.
Soltution
#Query(value = "SELECT ss.* FROM PARENT_TABLE ss\n" +
"left join CHILD_TABLE spe\n" +
"ON ss.ID= spe.SIGNING_STATUS_ID\n" +
"and spe.phase IN ('Value1', 'Value2')\n" +
"where spe.ID is null", nativeQuery = true)
PARENT_ENTITY[] customQuery();
This will return all parent entities whose children's property phase don't have any one of the value 'Value1' or 'Value2' or both.

Combine parameters and hardcoded clauses in Spring JPA #Query

I have a user repository in the application which works nicely for cases like this one:
#Query(" FROM UserEntity ue WHERE ue.site.id = :site_id
and ue.name = :username")
User findByUsername(#Param("site_id") String siteId,
#Param("username") String userName);
There is now a new option in one of the user fields, which should prevent the user to appear anywhere in the application. So instead of modifying the whole application, I've decided to modify just the queries in repositories with hardcoded clause like this:
#Query(" FROM UserEntity ue WHERE ue.site.id = :site_id
and ue.name = :username and ue.state != 'Disabled'")
User findByUsername(#Param("site_id") String siteId,
#Param("username") String userName);
(The changed part is and ue.state != 'Disabled')
The problem is, that such query doesn't return anything no matter what the value of state is.
I've also tried ue.state not like 'Disabled', but with the same result.
I've seen a lot of examples of using #Query, but didn't find any with hardcoded clauses. Is that even possible?
Yes you can pass the hardcoded values without changing the method signature.
JPA repository
#Query(" FROM Users ue WHERE ue.userLogin = :userLogin and ue.userName != 'admin'")
public Users cehckeme(#Param("userLogin") String userLogin);
Test Code
ApplicationContext ctx = new ClassPathXmlApplicationContext(
"spring_model.xml");
usersRepository = (UsersRepository) ctx.getBean("usersRepository");
System.out.println(usersRepository.cehckeme("pthakre").getUserLogin());
Genrated query
Hibernate:
select
*
from
( select
users0_.USER_ID as USER1_2_,
users0_.USER_LOGIN as USER2_2_,
users0_.USER_NAME as USER3_2_,
users0_.USER_PASSWORD as USER4_2_
from
MED_USERS users0_
where
users0_.USER_LOGIN=?
and users0_.USER_NAME<>'admin' )
where
rownum <= ?

Use WHERE clause with instances in hql

How to select a record from a table using WHERE clause and comparing instances (patient)
public History findHistory(Patient patient) { History model=null;
Session sesion=util.HibernateUtil.getSessionFactory().getCurrentSession();
String sql="FROM History h WHERE h.patients=" + patient;
try{
sesion.beginTransaction();
model=(History) sesion.createQuery(sql).uniqueResult();
sesion.beginTransaction().commit();
}
catch(Exception e){
sesion.beginTransaction().rollback();
}
return model;
}
That throws a queryException #1562
e.queryString="FROM entidad.Historia h WHERE h.pacientes=entidad.Paciente#3ad3a221"
e.detailMessage="unexpected char: '#'"
The problem with your code is that concatenating patient like you do will just append patient.toString(), which in your case is the default implementation (i.e. classname#hashcode) and it is no use for Hibernate to find out which data to retrieve in the DB.
You need to bind the parameter, first:
String sql = "FROM History h WHERE h.patients = :patient";
Then
model = (History) sesion.createQuery(sql)
.setParameter("patient", patient)
.uniqueResult();
Edit:
SQLGrammarException: could not execute query can occurs for various reason. Try to run the generated query in SqlDeveloper (or any other tool) and see what your DB says. In your case, the last part and .=? cause the error. The cross join is Harming too. I suspect your mapping is incomplete and Hibernate can't find how to join History and Patient. Try to add something like this in History entity:
#ManyToOne
#JoinColumn(name = "patient")
private Patient patient;

Resources