Object is already used when use same q-object in QueryDSL - java

Hi I have Hibernate project with QUeryDSL 3.6.0, when I have in my service class only findAll() methods, everyThing was OK. But whe I add findByID an error appears.
public class ArticleServiceImpl extends ArticleService {
QArticle article = QArticle.article;
#Override
public List<Article> findAll() {
return query.from(article).fetchAll().list(article);
}
public Article findById(#Nonnull final long id) {
return query.from(article).where(article.id.eq(id)).uniqueResult(article);
}
}
An the error is:
Exception in thread "main" java.lang.IllegalStateException: article is already used
at com.mysema.query.DefaultQueryMetadata.addJoin(DefaultQueryMetadata.java:160)
at com.mysema.query.support.QueryMixin.from(QueryMixin.java:189)
at com.mysema.query.jpa.JPAQueryBase.from(JPAQueryBase.java:88)
at com.mysema.query.jpa.JPAQueryBase.from(JPAQueryBase.java:32)
at com.example.hibernate.services.ArticleServiceImpl.findById(ArticleServiceImpl.java:30)
at com.example.hibernate.core.Main.main(Main.java:42)
What happens? I saw that queries are not tread-safe. But how to use Q-class in the two different methods?
Edit:
protected JPQLQuery query = new JPAQuery(entityManager);
it is protected variable that comes from ArticleService.

This exception is thrown whenever there is a repeatable call to the same generated QEntity in from() clause for the same instance of JPAQuery().
Here is an example (DISCLAIMER: This is going to be very, very dumb example just to illustrate the problem).
Let's say we have an entity called MyEntity, and we try to get two MyEntity's from a database in a way, that the first result will be for a given id, and the second result will be the one which has id+1
public List<MyEntity> findMyDumbEntities(long id) {
QMyEntity qMyEntity = QMyEntity.myEntity;
JPAQuery jpaQuery = new JPAQuery(entityManager);
MyEntity myFirstEntity = jpaQuery.from(qMyEntity).where(qMyEntity.id.eq(id)).uniqueResult(qMyEntity);
MyEntity mySecondEntity = jpaQuery.from(qMyEntity).where(qMyEntity.id.eq(id+1)).uniqueResult(qMyEntity);
return Arrays.asList(myFirstEntity, mySecondEntity);
}
And when trying to call this method we will see the following exception:
java.lang.IllegalStateException: qMyEntity is already used
Why? Because we have one instance of JPAQuery and we are repeating call to the same entity (we have two jpqQuery.from(qMyEntity)). To solve the problem we just need to get JPAQuery instance each time we want to query something, so we need to change our code to
public List<MyEntity> findMyDumbEntities(long id) {
QMyEntity qMyEntity = QMyEntity.myEntity;
MyEntity myFirstEntity = new JPAQuery(entityManager).from(qMyEntity).where(qMyEntity.id.eq(id)).uniqueResult(qMyEntity);
MyEntity mySecondEntity = new JPAQuery(entityManager).from(qMyEntity).where(qMyEntity.id.eq(id+1)).uniqueResult(qMyEntity);
return Arrays.asList(myFirstEntity, mySecondEntity);
}
So to fix your problem, instead of having JPQQuery initialized once
protected JPQLQuery query = new JPAQuery(entityManager);
Change that you get each time new JPAQuery, for example
protected JPQLQuery jpaQuery() {
return new JPAQuery(entityManager);
}
And then in your service implementation
#Override
public List<Article> findAll() {
return jpaQuery().from(article).fetchAll().list(article);
}
public Article findById(#Nonnull final long id) {
return jpaQuery().from(article).where(article.id.eq(id)).uniqueResult(article);
}

Related

What caused the PersistenceException with the message "detached entity passed to perist"

I'm using:
Quarkus with JPA (javax)
Postgres 11 database
I have:
An Entity
#Entity
#Table(name = "MyEntityTable")
#NamedQuery(name = MyEntity.DOES_EXIST, query = "SELECT x FROM MyEntity x WHERE x.type = :type")
public class MyEntity {
public static final String DOES_EXIST = "MyEntity.DoesExists";
#Id
#SequenceGenerator(name = "myEntitySequence", allocationSize = 1)
#GeneratedValue(generator = myEntitySequence)
private long id;
#Column(name = type)
private String type;
}
A repository
#ApplicationScoped
#Transactional(Transactional.TxType.Supports)
public class MyEntityReporitory {
#Inject
EntityManager entityManager;
#Transactional(Transactional.TxType.Required)
public void persist(final MyEntity entity) {
entityManager.persist(entiy);
}
public boolean doesExist(final String type) {
final TypedQuery<MyEntity> query = entityManager
.createNamedQuery(MyEntity.DOES_EXIST, MyEntity.class)
.setParameter("type", type);
return query.getResultList().size() > 0;
}
}
A test with two variations
Variation 1
#QuarkusTest
#QuarkusTestResource(DatabaseResource.class) // used to set up a docker container with postgres db
public class MyEntityRepositoryTest {
private static final MyEntity ENTITY = entity();
#Inject
MyEntityRepository subject;
#Test
public void testDoesExist() {
subject.persist(ENTITY);
final boolean actual = subject.doesExist("type");
assertTrue(actual);
}
#Test
public void testDoesExist_notMatching() {
subject.persist(ENTITY);
final boolean actual = subject.doesExist("another_type");
assertFalse(actual);
}
private static MyEntity entity() {
final MyEntity result = new MyEntity();
result.setType("type")
return result;
}
}
When I execute this test class (both tests) I'm getting the following Exception on the second time the persist method is called:
javax.persistence.PersistenceException: org.hibernate.PersistentObjectException: detached entity passed to persist com.mypackage.MyEntity
...
Variation 2
I removed the constant ENTITY from the test class, instead I'm calling now the entity() method inside the tests, like:
...
subject.persist(entity());
...
at both places. Now the Exeption is gone and everything is fine.
Question
Can someone explain to me, why this is the case (why variante 2 is working and variante 1 not)?
https://vladmihalcea.com/jpa-persist-and-merge/
The persist operation must be used only for new entities. From JPA perspective, an entity is new when it has never been associated with a database row, meaning that there is no table record in the database to match the entity in question.
testDoesExist executed, ENTITY saved to database and ENTITY.id set to 1
testDoesExist_notMatching executed and persist called on ENTITY shows the error beacuse it exists in the database, it has an id assigned
The simplest fix is to call entity() twice, as in you variation 2.
But don't forget that the records will exist after a test is run, and might affect your other test cases. You might want to consider cleaning up the data in an #After method or if you intend to use this entity in multiple test cases then put the perist code into a #BeforeClass method.

Spring data JPA #Query mapping with named columns

I use Spring Boot 1.5 and spring data JPA with MySQL. I tried to run a simple counting query on a single table, but could not find a better way to map the Query results than this.:
Repository:
public interface VehicleRepository extends JpaRepository<Vehicle, String> {
#Query("select v.sourceModule as sourceModule, count(v) as vehicleCount from Vehicle v group by v.sourceModule")
List<Object[]> sourceModuleStats();
}
Service:
#Override
public List<SourceModuleStatDTO> getSourceModuleStats() {
List<Object[]> objects = vehicleRepository.sourceModuleStats();
return objects.stream()
.map(o->SourceModuleStatDTO.from((String)o[0], (Long)o[1]))
.collect(Collectors.toList());
}
I use org.immutables, so the DTO.:
#Value.Immutable
#JsonSerialize(as = ImmutableSourceModuleStatDTO.class)
#JsonDeserialize(as = ImmutableSourceModuleStatDTO.class)
public abstract class SourceModuleStatDTO {
public abstract String sourceModule();
public abstract long vehicleCount();
public static SourceModuleStatDTO from(String sm, long c) {
return ImmutableSourceModuleStatDTO.builder()
.sourceModule(sm)
.vehicleCount(c)
.build();
}
}
The problem here is the mapping, I need to cast the results or manually check everything. Even JdbcTemplate has better mapping capabilities, I can't believe there is no better way to do this.
I tried this too: https://stackoverflow.com/a/36329166/840315 , but you need to hard code classpaths into the Query to get it work and also I would still need to map the objects to Immutables.
Using JdbcTemplate, you can use the RowMapper (src) :
private static final class EmployeeMapper implements RowMapper<Employee> {
#Override
public Employee mapRow(ResultSet rs, int rowNum) throws SQLException {
Employee employee = new Employee();
employee.setCountry(rs.getString("country"));
employee.setEmployeeName(rs.getString("employee"));
return employee;
}
}
Is there something similar for spring data JPA #Query?
How about using Projections as below?
static interface VehicleStats {
public String getSourceModule();
public Long getVehicleCount();
}
And your repository method would be
#Query("select v.sourceModule as sourceModule, count(v) as vehicleCount from Vehicle v group by v.sourceModule")
List<VehicleStats> sourceModuleStats();
In your Service class, you can use the interface methods as below.
List<VehicleStats> objects = vehicleRepository.sourceModuleStats();
return objects.stream()
.map(o->SourceModuleStatDTO.from(getSourceModule(),getVehicleCount() )
.collect(Collectors.toList());

Hibernate Filter is not applied for FindOne CRUD operation

I do have this hibernate filter in my repository:
#Entity
#Audited
#DiscriminatorValue(value = "GROUP")
#FilterDef(name = "groupACL", parameters = #ParamDef(name = "userId", type = "long"))
#Filters({
#Filter(name = "groupACL", condition = "app_group_id IN (SELECT DISTINCT APP_GROUP_ID FROM APP_GROUP START WITH APP_GROUP_ID IN (SELECT UG.APP_GROUP_ID FROM USER_GROUP UG JOIN APP_USER AU ON AU.APP_USER_ID = UG.APP_USER_ID WHERE USER_ID=:userId) CONNECT BY PARENT_APP_GROUP_ID = PRIOR APP_GROUP_ID)", deduceAliasInjectionPoints = false) })
public class Group extends AbstractGroup {
It is triggered using Spring AOP with the following class:
#Component
#Aspect
public class ACLFilterAspect {
private static final String GROUP_ACL = "groupACL";
#Autowired
private EntityManager em;
#Before("execution(* com.trendshift.kyn.pug.data.GroupRepository.*(..))")
public void forceFilter() {
Session hibernateSession = em.unwrap(Session.class);
....
hibernateSession.enableFilter(GROUP_ACL).setParameter("userId", userId);
}
}
}
I finally have the following service:
#Service
#Transactional(propagation = Propagation.REQUIRED)
public class GroupServiceImpl implements GroupService {
#Autowired
GroupRepository groupRepository;
#Override
public Group findGroupById(long id) {
Group group = groupRepository.findById(id);
return group;
}
}
and these repositories:
#RepositoryRestResource(exported = false)
public interface AbstractGroupRepository<T extends AbstractGroup>
extends JpaRepository<T, Long>, QueryDslPredicateExecutor<T> {
List<T> findByNameIgnoreCase(String name);
List<T> findByNameAndTypeOrderByNameAsc(String name, String type);
List<T> findByIdOrderByNameAsc(Long id);
AbstractGroup findById(Long id);
}
public interface GroupRepository
extends AbstractGroupRepository<Group>, GroupRepositoryExtras {
List<Group> findByNameAndCustomerId(String name, Long customerId);
Iterable<Group> findByCustomerIdAndTypeIn(Long id, List<String> types);
Group findById(long id);
}
The issue is that when I use groupRepository.findById(id) the filter is correctly applied.
If I use a CRUD core query groupRepository.findOne(id) the filter is not applied even after processing the Aspect hibernateSession.enableFilter(GROUP_ACL).setParameter("userId", userId);
Even if Java enables the filter, the log file doesn't show any trace of the filter in the hibernate query.
The problem seem to be only with the .findOne. findAll is working fine.
Is there something in the Hibernate doc that says that you cannot applied a filter to findOne methods?
I used filters to restrict user access to some information based on entity attributes. This was why I wanted even the findOne to respect the filters.
This was the prettiest way that I found to solve this "problem".
ClassPool pool = ClassPool.getDefault();
try {
CtClass cl = pool.get("org.hibernate.loader.plan.exec.internal.EntityLoadQueryDetails");
CtMethod me = cl.getDeclaredMethod("applyRootReturnFilterRestrictions");
String s = "{final org.hibernate.persister.entity.Queryable rootQueryable = (org.hibernate.persister.entity.Queryable) getRootEntityReturn().getEntityPersister();" +
"$1.appendRestrictions(" +
"rootQueryable.filterFragment(" +
"entityReferenceAliases.getTableAlias()," +
"getQueryBuildingParameters().getQueryInfluencers().getEnabledFilters()" +
"));}";
me.setBody(s);
cl.toClass();
} catch (Exception e) {}
To answer the actual Question:
Is there something in the Hibernate doc that says that you cannot applied a filter to findOne methods?
Yes, there is. From the Hibernate docs
Filters apply to entity queries, but not to direct fetching.
Therefore, in the following example, the filter is not taken into consideration when fetching an entity from the Persistence Context.
entityManager
.unwrap( Session.class )
.enableFilter( "activeAccount" )
.setParameter( "active", true);
Account account = entityManager.find( Account.class, 2L );
assertFalse( account.isActive() );
The implementation of for e.g SimpleJpaRepository.java in Spring uses em.find under the hood. Therefore the request is not filtered.
BUT if you override the implementation somehow (e.g. by using a projection or by writing a custom query), so that a query is generated, the request will be filtered.
This behaviour can be pretty confusing.
I ended up listening for any access to the CRUDRepository class. Not sure if that's the best way but that solves my issue.
#Component
#Aspect
public class ACLFilterAspect {
private static final String GROUP_ACL = "groupACL";
#Autowired
private EntityManager em;
#Before("||execution(* *(..)) && this(com.trendshift.kyn.pug.data.GroupRepository)"
+ "||execution(* *(..)) && this(org.springframework.data.repository.CrudRepository)")
Just override findById and use getById instead
#Repository
public interface CustomerRepository extends JpaRepository<Customer, Long>, JpaSpecificationExecutor<Customer>, SupportedRepositoryOperation<Customer> {
default Optional<Customer> findById(Long aLong) {
throw new OperationFindByIdNotAllowedException();
}
Optional<Customer> getById(Long id);
}

Spring Pageable doesn't work for ordering

On the internet I found that Spring can do pagination as well as ordering for a list of data retrieved from the database. Accordingly, I created my test class as following:
#Test
public void testPageable() {
int pageSize = 5;
Sort sort = new Sort( Direction.DESC, "someColumnA" );
Pageable pageable = new PageRequest( 0, pageSize, sort );
List<SomeObject> listOFSomeObject = getDao().getListData( "paramOne", pageable );
}
When I analyze the List I never get ordering of someColumnA in a DESC fashion, although I get back only 5 records which is correct.
Can someone please let me know what I might be doing wrong? Just as an FYI, I am using Hibernate for database access and Spring named query.
EDIT:
Code for getListData()->
public interface SomeRepository
extends JpaRepository<EntityMappedViaHibernate, String> {
List<Object[]> getListData( #Param(value = PARAM_ONE) final String paramOne, Pageable pageable );
}
My Hibernate entity is as follows:
#NamedQueries(value = {
#NamedQuery(name = "SomeRepository.getListData", query = "select id, someColumnA from EntityMappedViaHibernate where id = :paramOne")
})
#Entity
#Table(name = "entity_mapped_via_hibernate")
public class EntityMappedViaHibernate implements Serializable {
// Code Omitted on purpose
}
So those of you who are struggling like me the solution to this problem is that you need to have the query inside the Repository. In my case it has to be inside the SomeRepository. So in the code of the repo do the following:
public interface SomeRepository
extends JpaRepository<EntityMappedViaHibernate, String> {
#Query("select id, someColumnA from EntityMappedViaHibernate where id = :paramOne")
List<Object[]> getListData( #Param(value = PARAM_ONE) final String paramOne, Pageable pageable );
}
No need to have the NamedQuery inside the EntityMappedViaHibernate. That's it!!
Hope someone find the answer and do not struggle like I did.

How to optimize hibernate method call in loop?

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.

Categories

Resources