spring data #query 'cached' table? - java

App entity
#Entity
#Table(name = "app")
#Cache(usage = CacheConcurrencyStrategy.NONSTRICT_READ_WRITE)
public class App implements Serializable {
#ManyToMany
#Cache(usage = CacheConcurrencyStrategy.NONSTRICT_READ_WRITE)
#JoinTable(name = "app_alloweduser",
joinColumns = #JoinColumn(name="apps_id", referencedColumnName="id"),
inverseJoinColumns = #JoinColumn(name="allowedusers_id", referencedColumnName="id"))
private Set<User> allowedusers = new HashSet<>();
}
I try to write my own query as such.
#Query("SELECT app FROM App app WHERE (:department is null or app.department = :department) and "
+ "(:platform is null or app.platform = :platform) and "
+ "(:appName is null or lower(app.appName) LIKE CONCAT('%',lower(:appName),'%')) and "
+ "(app.id in (SELECT b.apps_id FROM allowedusers b where b.allowedusers_id = :loginid))")
Page<App> findAllAllowed(#Param("department") Department department,
#Param("platform") Platform platform,
#Param("appName") String appName,
#Param("loginid") Long loginid,
Pageable pageable);
Error: org.hibernate.hql.internal.ast.QuerySyntaxException: allowedusers is not mapped
I have tried app_alloweduser also and it too throws the same error that it can't map. The table APP_ALLOWEDUSER, which is what I am interested in is verified to be there though. So I am wondering what is going on here.
Some background
The table app_alloweduser has 2 column, app_id & user_id. I will first have to retrieve a list of app_id given a user_id. Then return all app from this list of app_id.

Try to replace FROM allowedusers by FROM app.allowedusers

There is no alloweduser entity only App and User. Try to use join instead.
#Query("SELECT app FROM App app join User u WHERE (:department is null or app.department = :department) and "
+ "(:platform is null or app.platform = :platform) and "
+ "(:appName is null or lower(app.appName) LIKE CONCAT('%',lower(:appName),'%')) and "
+ "(u.allowedusers_id = :loginid))")
UPDATE:
change u.allowedusers_id = :loginid to u.id = :loginid.
You don't have the table in HQL. Of course you can introduce a new entity if you need for the APP_ALLOWEDUSER

I found out that I can access the table via a.allowedusers
Given the following declaration or particularly this line,
inverseJoinColumns = #JoinColumn(name="allowedusers_id", referencedColumnName="id"))
I can access the database equivalent of app_alloweduser.allowedusers_id using a.allowedusers.id on JPQL.
#JoinTable(name = "app_alloweduser",
joinColumns = #JoinColumn(name="apps_id", referencedColumnName="id"),
inverseJoinColumns = #JoinColumn(name="allowedusers_id", referencedColumnName="id"))
private Set<User> allowedusers = new HashSet<>();
Final Query
#Query("SELECT a FROM App a JOIN a.allowedusers au WHERE au.id = :loginid AND "
+ "(:department is null or a.department = :department) AND "
+ "(:platform is null or a.platform = :platform) AND "
+ "(:appName is null or lower(a.appName) LIKE CONCAT('%',lower(:appName),'%'))")
Page<App> findAllAllowed(#Param("department") Department department,
#Param("platform") Platform platform,
#Param("appName") String appName,
#Param("loginid") Long loginid,
Pageable pageable);

Related

Querying one to many JPA for an entity in a list

I have a class called Customer and it contains a list of Account. I am trying to write a query using JPA to perform a search.
#Entity
class Customer {
private var id: Long = 0
private var name: String? = null
#OneToMany(fetch = FetchType.LAZY, cascade = [CascadeType.ALL])
#JoinTable(
name = "customer_account",
joinColumns = [javax.persistence.JoinColumn(name = "customerId", referencedColumnName = "id")],
inverseJoinColumns = [JoinColumn(name = "accountId", referencedColumnName = "id")]
)
private var account: List<Account>
}
#Entity
class Account {
private var id: Long =0
private var amount: Long = 0
}
I am trying to run a query that will return a customer object if the account id matches the input.
So in the repository I am trying to:
#Query("SELECT c from Customer as c WHERE c.account.id = :id)
fun findCustomerByAccountId(id: Long): Customer
However, I am NOT seeing account.id I am instead seeing size which tells me that the invocation is on the Account List . Is there a way to actually perform this search?
#Query("SELECT c from Customer as c WHERE c.account.id = :id)
fun findCustomerByAccountId(id: Long): Customer
Your query seems fine but as this result will return a list, you should define it in your method[though I am not familiar with this syntax].
However, I do this like this way:
#Query("SELECT c from Customer as c WHERE c.account.id = :id)
List<Customer> findCustomerByAccountId(id: Long): Customer

Nested fetch join with JPQL and Hibernate

I am writing a JPQL query (with Hibernate as my JPA provider) to fetch an entity Company and several of its associations. This works fine with my "simple" ManyToMany associations, like so:
#Entity
#Table(name = "company")
#NamedQueries({
#NamedQuery(
name = "Company.profile.view.byId",
query = "SELECT c " +
"FROM Company AS c " +
"INNER JOIN FETCH c.city AS city " + <-- #ManyToOne
"LEFT JOIN FETCH c.acknowledgements " + <-- #ManyToMany
"LEFT JOIN FETCH c.industries " + <-- #ManyToMany
"WHERE c.id = :companyId"
)
})
public class Company { ... }
Hibernate creates a single query to fetch the above, which is good. However, my Company entity also has a many-to-many association with data stored in the intermediate table, hence why this is mapped as #OneToMany and #ManyToOne associations between three entities.
Company <-- CompanyService --> Service
These are the three entities that I have in my code. So a Company instance has a collection of CompanyService entities, which each has a relation to a Service instance. I hope that makes sense - otherwise please check the source code at the end of the question.
Now I would like to fetch the services for a given company by modifying the above query. I read in advance that JPA doesn't allow nested fetch joins or even aliases for joins, but that some JPA providers do support it, and so I tried my luck with Hibernate. I tried to modify the query as such:
#Entity
#Table(name = "company")
#NamedQueries({
#NamedQuery(
name = "Company.profile.view.byId",
query = "SELECT c " +
"FROM Company AS c " +
"INNER JOIN FETCH c.city AS city " +
"LEFT JOIN FETCH c.acknowledgements " +
"LEFT JOIN FETCH c.industries " +
"LEFT JOIN FETCH c.companyServices AS companyService " +
"LEFT JOIN FETCH companyService.service AS service " +
"WHERE c.id = :companyId"
)
})
public class Company { ... }
Now, instead of creating a single query, Hibernate creates the following queries:
#1
select ...
from company company0_
inner join City city1_ on company0_.postal_code = city1_.postal_code
[...]
left outer join company_service companyser6_ on company0_.id = companyser6_.company_id
left outer join service service7_ on companyser6_.service_id = service7_.id
where company0_.id = ?
#2
select ...
from company company0_
inner join City city1_ on company0_.postal_code = city1_.postal_code
where company0_.id = ?
#3
select service0_.id as id1_14_0_, service0_.default_description as default_2_14_0_, service0_.name as name3_14_0_
from service service0_
where service0_.id = ?
#4
select service0_.id as id1_14_0_, service0_.default_description as default_2_14_0_, service0_.name as name3_14_0_
from service service0_
where service0_.id = ?
Query #1
I left out the irrelevant joins as these are OK. It appears to select all of the data that I need, including the services and the intermediate entity data (CompanyService).
Query #2
This query simply fetches the company from the database and its City. The city association is eagerly fetched, but the query is still generated even if I change it to lazy fetching. So honestly I don't know what this query is for.
Query #3 + Query #4
These queries are looking up Service instances based on ID, presumably based on service IDs fetched in Query #1. I don't see the need for this query, because this data was already fetched in Query #1 (just as the data from Query #2 was already fetched in Query #1). Also, this approach obviously does not scale well if a company has many services.
The strange thing is that it seems like query #1 does what I want, or at least it fetches the data that I need. I just don't know why Hibernate creates query #2, #3 and #4. So I have the following questions:
Why does Hibernate create query #2, #3 and #4? And can I avoid it?
Does Hibernate support nested association fetching even though JPA doesn't? If so, how would I go about it in my case?
Is this behavior normal, or is it because what I am trying to do is just not supported, and therefore I get weird results? This would seem odd, because query #1 looks perfectly fine
Any pointers of mistakes or alternative solutions to accomplish what I want would be much appreciated. Below is my code (getters and setters excluded). Thanks a lot in advance!
Company entity
#Entity
#Table(name = "company")
#NamedQueries({
#NamedQuery(
name = "Company.profile.view.byId",
query = "SELECT c " +
"FROM Company AS c " +
"INNER JOIN FETCH c.city AS city " +
"LEFT JOIN FETCH c.acknowledgements " +
"LEFT JOIN FETCH c.industries " +
"LEFT JOIN FETCH c.companyServices AS companyService " +
"LEFT JOIN FETCH companyService.service AS service " +
"WHERE c.id = :companyId"
)
})
public class Company {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column
private int id;
// ...
#ManyToOne(fetch = FetchType.EAGER, targetEntity = City.class, optional = false)
#JoinColumn(name = "postal_code")
private City city;
#ManyToMany(fetch = FetchType.LAZY)
#JoinTable(name = "company_acknowledgement", joinColumns = #JoinColumn(name = "company_id"), inverseJoinColumns = #JoinColumn(name = "acknowledgement_id"))
private Set<Acknowledgement> acknowledgements;
#ManyToMany(fetch = FetchType.LAZY)
#JoinTable(name = "company_industry", joinColumns = #JoinColumn(name = "company_id"), inverseJoinColumns = #JoinColumn(name = "industry_id"))
private Set<Industry> industries;
#OneToMany(fetch = FetchType.LAZY, mappedBy = "company")
private Set<CompanyService> companyServices;
}
CompanyService entity
#Entity
#Table(name = "company_service")
#IdClass(CompanyServicePK.class)
public class CompanyService implements Serializable {
#Id
#ManyToOne(targetEntity = Company.class)
#JoinColumn(name = "company_id")
private Company company;
#Id
#ManyToOne(targetEntity = Service.class)
#JoinColumn(name = "service_id")
private Service service;
#Column
private String description;
}
Service entity
#Entity
#Table(name = "service")
public class Service {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column
private int id;
#Column(length = 50, nullable = false)
private String name;
#Column(name = "default_description", nullable = false)
private String defaultDescription;
}
Fetching data
public Company fetchTestCompany() {
TypedQuery<Company> query = this.getEntityManager().createNamedQuery("Company.profile.view.byId", Company.class);
query.setParameter("companyId", 123);
return query.getSingleResult();
}
Okay, it seems like I figured it out. By setting the fetch type to FetchType.LAZY in CompanyService, Hibernate stopped generating all of the redundant queries that were basically fetching the same data again. Here is the new version of the entity:
#Entity
#Table(name = "company_service")
#IdClass(CompanyServicePK.class)
public class CompanyService implements Serializable {
#Id
#ManyToOne(fetch = FetchType.LAZY, targetEntity = Company.class)
#JoinColumn(name = "company_id")
private Company company;
#Id
#ManyToOne(fetch = FetchType.LAZY, targetEntity = Service.class)
#JoinColumn(name = "service_id")
private Service service;
#Column
private String description;
}
The JPQL query remains the same.
However, in my particular case with the number of associations my Company entity has, I was getting a lot of duplicated data back, and so it was more efficient to let Hibernate execute an additional query. I accomplished this by removing the two join fetches from my JPQL query and changing my query code to the below.
#Transactional
public Company fetchTestCompany() {
TypedQuery<Company> query = this.getEntityManager().createNamedQuery("Company.profile.view.byId", Company.class);
query.setParameter("companyId", 123);
try {
Company company = query.getSingleResult();
Hibernate.initialize(company.getCompanyServices());
return company;
} catch (NoResultException nre) {
return null;
}
}
By initializing the companyServices association, Hibernate executes another query to fetch the services. In my particular use case, this is better than fetching a ton of redundant data with one query.
I hope this helps someone. If anyone has any better solutions/improvements, then I would of course be happy to hear them.
From what you wrote, I would say that nested fetching isn't supported. This is my understanding of your results:
Query #1 is ok, and joins everything that it needs, this is good
However, Query #2 I think gets CompanyService#company (with eager city resulting in inner join City)
Query #3 gets CompanyService#service
Query #4 is a mistery to me
I know this is not an answer, but it might help you understand what's going on in the background.

EclipseLink: The field in this expression has an invalid table in this context

I get an exception with the description "The field [EMPLOYEE.ID] in this expression has an invalid table in this context.", when trying to execute this query, which should select all acivities of the given employee
final Query query = getEm()
.createQuery(
"SELECT a from Activity a WHERE a IN (SELECT ae FROM a.employees ae) "
+ "AND ?1 IN (SELECT ae FROM a.employees ae)");
query.setParameter(1, employee);
return (List<Activity>) query.getResultList();
This is the map annotation in my class Activity:
#OneToMany
#JoinTable(name = "ACTIVITY_EMPLOYEES", joinColumns = #JoinColumn(name = "ACTIVITY_ID"), inverseJoinColumns = #JoinColumn(name = "EMPLOYEETIMEPERIODS_ID"))
#MapKeyJoinColumn(name = "EMPLOYEE_ID", table = "ACTIVITY_EMPLOYEES")
private Map<Employee, EmployeeTimePeriods> employees = new HashMap<>();
Any suggestions how I could solve this? Is there maybe another way, to get all activities of an given employee?
Thanks in advance.

Why cannot join 3rd table using JPA TopLink annotation?

I have simple 3 tables:
Users
-id
-name
Addresses
-id
-user_id //FK
-location
Phone
-id
-number
-address_id //FK
Users can have many Addresses
Addresses can have many Phone
Thus, annotations applied to each entity class would look like:
Users
#OneToMany(cascade = CascadeType.ALL, mappedBy = "user")
private List<Addresses> addresses;
Addresses
#ManyToOne()
#JoinColumn(name = "user_id")
private Users user;
#OneToMany(mappedBy = "address")
private List<Phone> phone;
Phone
#ManyToOne()
#JoinColumn(name = "address_id")
private Addresses address;
The annotation above should create relationship:
users (1) ----- (n) addresses (1) ------ (n) phone
Then I created some test data in MySQL and tried to run test to see if query result that returns "users" would include reference to addresses and phone.
I've tried with 2 table only using users and addresses and got result ok.
Now that I added "phone" table which is 3rd table then I'm getting error shown below.
Is this really right way to join 3 or more tables?
Test code
try {
//get entity manager
String WEB_PU = "NoJSFPU";
EntityManagerFactory factory = Persistence.createEntityManagerFactory(WEB_PU);
EntityManager em = factory.createEntityManager();
//run query
List<Users> usersList = em.createQuery("SELECT u FROM Users u").getResultList();
//loop to print out each result
for(Users item : usersList) {
//display user info
System.out.println(item.getId() + " " + item.getFirstname());
List<Addresses> addrs = item.getAddresses();
for(Addresses ad : addrs) {
//display address info
System.out.println("Address:::" + ad.getLocation());
List<Phone> pho = ad.getPhone();
for(Phone p : pho) {
//display phone info
System.out.println("phone#" + p.getNumber());
}
}//end inner for
}//end outer for
System.out.println("==========================");
}
catch(Exception e) {
System.out.println("seriously wtf: " + e.toString());
System.exit(1);
}
Error
Exception Description: predeploy for PersistenceUnit [NoJSFPU] failed.
Internal Exception: Exception [TOPLINK-7250] (Oracle TopLink Essentials - 2.0.1 (Build b09d-fcs (12/06/2007))): oracle.toplink.essentials.exceptions.ValidationException
Exception Description: [class local.test.session.Addresses] uses a non-entity [class local.test.session.Phone] as target entity in the relationship attribute [private java.util.List local.test.session.Addresses.phone].
I figured out. Had to specify by using #JoinTable annotation.
Now If I run the test code, data from all 3 tables can be obtained :D
Addresses
#ManyToOne
#JoinTable(name = "Addresses", joinColumns = {#JoinColumn(name="user_id",
referencedColumnName = "user_id") } )
private Users user;
Phone
#ManyToOne
#JoinTable(name = "Phone", joinColumns = {#JoinColumn(name="address_id",
referencedColumnName = "address_id") } )
private Addresses address;

how to write JPA query

Learning how to write JPA query. Please advise me whether it possible to write the below queries more efficiently, may be in a single select statement. May be a join, but not sure how to do it.
class Relationship {
#ManyToOne
public String relationshipType; //can be MANAGER, CUSTOMER etc
#ManyToOne
public Party partyFrom; // a person who has a relation
#ManyToOne
public Party partyTo; // a group a person relate to
}
Queries:
String sql = "";
sql = "select rel.partyTo";
sql += " from Relationship rel";
sql += " where rel.partyFrom = :partyFrom";
sql += " and rel.relationshipType= :typeName";
Query query = Organization.em().createQuery(sql);
query.setParameter("partyFrom", mgr1);
query.setParameter("typeName", "MANAGER");
List<Party> orgList = query.getResultList();
String sql2 = "";
sql2 = "select rel.partyFrom";
sql2 += " from Relationship rel";
sql2 += " where rel.partyTo = :partyToList";
sql2 += " and rel.relationshipType = :typeName2";
Query query2 = Organization.em().createQuery(sql2);
query2.setParameter("partyToList", orgList);
query2.setParameter("typeName2", "CUSTOMER");
List<Party> personList2 = query2.getResultList();
Both the queries work. Query 1 returns a list of groups, where the person (mgr1) has a relation MANAGER with. Query 2 returns all the Persons they are CUSTOMER to the groups returned by query 1. In effect, I get a list of Person they are belong to (customer) the same group where the Person (mgr1) has a relation MANAGER with.
Is it possible to combine them into single sql statement so possibly only one db access?
You literally nest one query inside the other, and use a "where in" clause to specify that the outer query should fetch customers from the inner query.
select rel2.partyFrom
from Relationship rel2
where rel2.relationshipType = :typeName2 /* customer */
and rel2.partyTo.id in
(select rel.partyTo.id
from Relationship rel
where rel.partyFrom = :partyFrom
and rel.relationshipType = :typeName)
Your calling code passes typeName, typeName2, and partyFrom parameters as before. PartyTo parameter is not needed, since the data comes from the subselect (inner query.)
You can achieve the same thing using a self join, with a where clause that filters managers on the left side, and customers on the right side, but using an 'in' clause is semantically clearer.
EDIT: I addded .id to the subselect, which I think is needed.
This is not answer to question but helping other folks in case if someone looking into #OneToMany relation in Spring Data JPA using JPQL, because the question is related to JPA so thought to share my 2-cents, apologize in advance
#Entity
#Table(name = "MY_CAR")
public class MyCar {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
#Column(name = "DESCRIPTION")
private String description;
#Column(name = "MY_CAR_NUMBER")
private String myCarNumber;
#Column(name = "RELEASE_DATE")
private Date releaseDate;
#OneToMany(cascade = { CascadeType.ALL })
#JoinTable(name = "MY_CAR_VEHICLE_SERIES", joinColumns = #JoinColumn(name = "MY_CAR_ID "), inverseJoinColumns = #JoinColumn(name = "VEHICLE_SERIES_ID"))
private Set<VehicleSeries> vehicleSeries;
public MyCar() {
super();
vehicleSeries = new HashSet<VehicleSeries>();
}
// set and get method goes here
#Entity
#Table(name = "VEHICLE_SERIES ")
public class VehicleSeries {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
#Column(name = "SERIES_NUMBER")
private String seriesNumber;
#OneToMany(cascade = { CascadeType.ALL })
#JoinTable(name = "VEHICLE_SERIES_BODY_TYPE", joinColumns = #JoinColumn(name = "VEHICLE_SERIES_ID"), inverseJoinColumns = #JoinColumn(name = "BODY_TYPE_ID"))
private Set<BodyType> bodyTypes;
public VehicleSeries() {
super();
bodyTypes = new HashSet<BodyType>();
}
// set and get method goes here
#Entity
#Table(name = "BODY_TYPE ")
public class BodyType implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
#Column(name = "NAME")
private String name;
// set and get method goes here
public interface MyCarRepository extends JpaRepository<MyCar, Long> {
public Set<MyCar> findAllByOrderByIdAsc();
#Query(value = "select distinct myCar from MyCar myCar "
+ "join myCar.vehicleSeries as vs join vs.bodyTypes as bt where vs.seriesNumber like %:searchMyCar% "
+ "or lower(bt.name) like lower(:searchMyCar) or myCar.bulletinId like %:searchMyCar% "
+ "or lower(myCar.description) like lower(:searchMyCar) "
+ "or myCar.bulletinNumber like %:searchMyCar% order by myCar.id asc")
public Set<MyCar> searchByMyCar(#Param("searchMyCar") String searchMyCar);
}
Some data in tables like
Select * from Vehicle_Series
ID SERIES_NUMBER
1 Yaris
2 Corolla
Select * from Body_Type
ID NAME
1 Compact
2 Convertible
3 Sedan

Categories