I am currently using JPA development project, but encountered a problem, please enlighten
Example:
#Query("
SELECT
new CustomerMaintain(c.content, u, c.createTime, c.updateTime)
FROM
CustomerMaintain c
JOIN
c.user u
WHERE
c.delFlag = FALSE AND c.customer.id = :customerId
")
CustomerMaintain.class DTO:
#Entity
#Table(name = "t_customer_maintain")
#DynamicUpdate
#JsonIgnoreProperties({"hibernateLazyInitializer", "handler", "delFlag"})
public class CustomerMaintain {
public CustomerMaintain() {
}
public CustomerMaintain(String content, User user, Date createTime, Date updateTime) {
this.content = content;
this.user = user;
this.createTime = createTime;
this.updateTime = updateTime;
}
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id;
#Column(columnDefinition = "VARCHAR(512) DEFAULT ''")
private String content;
#ManyToOne(optional=false)
private User user;
#ManyToOne(optional=false)
private Customer customer;
#Column(columnDefinition = "TINYINT(1) DEFAULT FALSE ",insertable = false,updatable = false)
private Boolean delFlag;
#Column(columnDefinition = "TIMESTAMP DEFAULT CURRENT_TIMESTAMP",insertable = false,updatable = false)
private Date createTime;
#Column(columnDefinition = "TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP",insertable = false,updatable = false)
private Date updateTime;
// omission getter setter method
}
I think there is only one sql query:
SELECT
c.content,
u.id,
u.name,
u.head,
c.createTime,
c.updateTime
FROM
t_customerMaintain c
INNER JOIN
t_user u ...
But hibernate query result is this:
select
customerma0_.content as col_0_0_,
user1_.id as col_1_0_, /* only user.id fields */
customerma0_.createTime as col_2_0_,
customerma0_.updateTime as col_3_0_
from
t_customer_maintain customerma0_
inner join
t_user user1_
...
select
user0_.id as id1_4_0_,
user0_.head as head4_4_0_,
user0_.name as name5_4_0_
from
t_user user0_
where
user0_.id=? /* user1_.id as col_1_0_ */
Question:
Why the first SQL query only user.id, and not user.id, user.name, user.head?
The last SQL query is superfluous.
I try to write like this:
SELECT
new CustomerMaintain(c.content, new User(u.id, u.name, u.head), c.createTime, c.updateTime)
But doing so throws something abnormally: new User(u.id, u.name, u.head)
Please help me, this problem has been bothering me for a long time.
Could you explain why you think you need the NEW operator? The type of c is already CustomerMaintain, so SELECT c is enough.
If you want to explicitly fetch the User, use JOIN FETCH instead of JOIN. This way, the extra query for User will not be executed. Alternatively, you could use Hibernate's #Fetch(FetchMode.JOIN) to force Hibernate to use a join query (instead of a separate select query) to load CustomerMaintain.user whenever needed.
If you need finer-grained control over which properties get populated in the query result, use a fetch graph or a load graph as a query hint. Don't use NEW with entity constructors as results returned from such queries will not become managed.
Related
I have 2 entities:
#Entity
class Order {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private int id;
#OneToMany(cascade = CascadeType.ALL, fetch = FetchType.LAZY)
private Set<Contract> contracts= new HashSet<>();
#Formula("(select count(m.ORDER_ID) from myschema.ORDER_CONTRACTS m where m.ORDER_ID = id)")
private Integer numberOfContracts; // this is basically contracts.size()
}
and
#Entity
class Contract {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private int id;
private String client;
// some other properties
}
When I want to get my orders ordered by numberOfContracts, hibernate generates this query for me
SELECT order0_.id AS id1_5_,
(SELECT COUNT(m.ORDER_ID)
FROM myschema.ORDER_CONTRACTS m
WHERE m.ORDER_ID = order0_.id) AS formula1_
FROM myschema.order order0_
ORDER BY (SELECT COUNT(m.ORDER_ID)
FROM myschema.ORDER_CONTRACTS m
WHERE m.ORDER_ID = order0_.id) DESC
and fails with
com.ibm.db2.jcc.am.SqlSyntaxErrorException: DB2 SQL Error: SQLCODE=-206, SQLSTATE=42703, SQLERRMC=ORDER0_.ID, DRIVER=4.27.25
When I replace the select in the ORDER BY with formula1_ like this:
SELECT order0_.id AS id1_5_,
(SELECT COUNT(m.ORDER_ID)
FROM myschema.ORDER_CONTRACTS m
WHERE m.ORDER_ID = order0_.id) AS formula1_
FROM myschema.order order0_
ORDER BY formula1_ DESC
I get the expected result.
Is there a way to tell hibernate to use the generated alias (formula1_) instead of replicating the formula in the order by?
EDIT:
How I get my query:
I'm using an org.springframework.web.bind.annotation.RestController. This controller offers a endpoint to get all Orders by a method like this:
#GetMapping("orders")
public List<Order> getOrders(Pageable pageable);
When I send a request like http://localhost:8080/api/orders/sort=numberOfContracts,desc&size=100&page=0
to the endpoint, the pageable contains the information about ordering. My contoller then calls my
public interface OrderRepository extends PagingAndSortingRepository<Order, Integer>
witch provides this method:
Page<Order> findAll(Pageable page);
After this point spring and hibernate do their magic.
What kind of HQL query are you using. Hibernate will just do what you tell it to do. You will have to use the HQL alias as well in the order by clause if you want the SQL alias to be used:
SELECT o.id, o.numberOfContracts as num
FROM Order o
ORDER BY num desc
Assume I have the next data model:
#Entity
public class User {
#Id
#GeneratedValue(strategy = GenerationType.SEQUENCE)
private Long id;
#OneToMany(mappedBy = "user", cascade = CascadeType.ALL)
private List<Item> items;
... getters, setters, equals and hashcode.
}
#Entity
public class Item {
#Id
#GeneratedValue(strategy = GenerationType.SEQUENCE)
private Long id;
#ManyToOne
#JoinColumn(name = "user")
private User user;
private Boolean deleted;
... getters, setters, equals and hashcode.
}
I need to query a certain user by id with non-deleted items. Can I do it via Spring Data Repositories?
I tried something like:
public interface UserRepository extends CrudRepository<User, Long> {
#Query("from User u left join u.items i on i.deleted = false where u.id = ?1")
List<User> findUserWithNonDeletedItems(Long userId);
}
But this approach generates 2 separate SQL queries:
select user0_.id as id1_1_0_, items1_.id as id1_0_1_, items1_.deleted as deleted2_0_1_, items1_.user as user3_0_1_ from user user0_ left outer join item items1_ on user0_.id=items1_.user and (items1_.deleted=0) where user0_.id=?
select items0_.user as user3_0_0_, items0_.id as id1_0_0_, items0_.id as id1_0_1_, items0_.deleted as deleted2_0_1_, items0_.user as user3_0_1_ from item items0_ where items0_.user=?
And as result, I receive user with deleted items in the item list.
Nothing wrong with creation of two separete queries. One is to get users from user table, other is to get items of related user from items table.
join clause is used to combine columns from one or more tables.
join u.items i on i.deleted = false is not a proper use. It should be on the where clause.
You should change the query this way:
#Query("from User u left join u.items i where i.deleted = false and u.id = ?1")
List<User> findUserWithNonDeletedItems(Long userId);
I have an Blog entity like:
#Entity
class Blog{
#Id
private Long id;
private String titie;
private String content;
#ManyToMany
#JoinTable(
name = "blog_tag_association",
joinColumns = #JoinColumn(name = "blog_id"),
inverseJoinColumns = #JoinColumn(name = "tag_id")
)
private Set<Tag> tags = new LinkedHashSet<>();
// ...
}
As you can see, i use a separate table to represent the relationship between Blog and Tag.
I've read this post and write my custom JPQL query to map my query result to DTO:
class BlogSummaryDTO{
private Long id;
private String title;
private Set<Tag> tags;
public BlogSummaryDTO(Long id, String title, Set<Tag> tags){
this.id = id;
this.title = title;
this.tags = tags;
}
}
And my custom JPQL like:
String query = "SELECT new com.okatu.rgan.blog.model.BlogSummaryDTO(" +
"b.id, b.title, " +
"b.tags) FROM Blog b";
It cannot work, the terminal told me that:
Caused by: java.sql.SQLSyntaxErrorException: You have an error in your SQL syntax;
check the manual that corresponds to your MySQL server version for the right syntax to use near 'as col_2_0_ from blog blog0_ inner join blog_tag_association tags1_ on blog0_.id' at line 1
The sql it generate was like:
Hibernate:
select
blog0_.id as col_0_0_,
blog0_.title as col_1_0_,
. as col_2_0_
from
blog blog0_
inner join
blog_tag_association tags1_
on blog0_.id=tags1_.blog_id
inner join
tag tag2_
on tags1_.tag_id=tag2_.id
where
blog0_.title like ?
or blog0_.title like ? limit ?
If i remove the tags property from the JPQL and DTO's constructor's parameter list, it works well.
So how should i write my custom JPQL to construct the DTO with #JoinTable property?
I've also observed the jpa-generated sql when do the BlogRepository::findById, it use a separate native sql to retrieve the Tag.
Hibernate:
select
blog0_.id as id1_0_0_,
blog0_.content as content2_0_0_,
blog0_.created_time as created_3_0_0_,
blog0_.modified_time as modified4_0_0_,
blog0_.summary as summary5_0_0_,
blog0_.title as title6_0_0_,
blog0_.author_id as author_i9_0_0_,
user1_.id as id1_5_1_,
user1_.created_time as created_2_5_1_,
user1_.username as username7_5_1_,
from
blog blog0_
left outer join
user user1_
on blog0_.author_id=user1_.id
where
blog0_.id=?
Hibernate:
select
tags0_.blog_id as blog_id1_1_0_,
tags0_.tag_id as tag_id2_1_0_,
tag1_.id as id1_4_1_,
tag1_.description as descript2_4_1_,
tag1_.title as title3_4_1_
from
blog_tag_association tags0_
inner join
tag tag1_
on tags0_.tag_id=tag1_.id
where
tags0_.blog_id=?
But i don't know what's the JPQL it generated.
We have a deleted column on our table, is it possible to check that every time this table is queried the query has a condition for this column?
Some googling with better keywords (soft-delete) it seems I could do this with #When annotation. not exactly what I was looking but seems close enough.
You can check out #Where annotation.
org.hibernate.annotations.Where
Example:
If there's an Account Entity
#Entity(name = "Account")
#Where( clause = "active = true" )
public static class Account {
#Id
private Long id;
#ManyToOne
private Client client;
#Column(name = "account_type")
#Enumerated(EnumType.STRING)
private AccountType type;
private Double amount;
private Double rate;
private boolean active;
//Getters and setters omitted for brevity
}
and if the following code is used for fetching the accounts.
List<Account> accounts = entityManager.createQuery(
"select a from Account a", Account.class)
.getResultList();
then the following SQL will be generated
SELECT
a.id as id1_0_,
a.active as active2_0_,
a.amount as amount3_0_,
a.client_id as client_i6_0_,
a.rate as rate4_0_,
a.account_type as account_5_0_
FROM
Account a
WHERE ( a.active = true )
Hibernate ORM 5.2.18.Final User Guide
I want to execute a query like this:
SELECT Table1.COL1,
Table1.COL2,
(SELECT SUM(Table2.COL3)
FROM Table2
WHERE Table2.UID = Table1.UID) SUMOF
FROM Table1;
How can I do it?
I usually create a Criteria add ProjectionList to it, to fill COL1 and COL2 only.
I have created a DetachedCriteria to calculate the sum...
Now, how to attach this detached criteria to the main one? My intuition says - it's some sort of Projection which needs to be added to the list, but I don't see how. Also, not sure how WHERE Table2.COL4 = Table1.COL5 of detached criteria will work.
Also, I'm sure this query might be written in different way, for example with join statement. It's still interesting if there's a way to run it like this.
DetachedCriteria and main Criteria
DetachedCriteria detachedCriteria = DetachedCriteria.forClass(Table2.class, "table2");
detachedCriteria
.setProjection(
Projections.projectionList()
.add(Projections.sum("table2.col3"), "sumCol3")
)
.add(Restrictions.eq("table2.uid", "table1.uid"))
;
Criteria criteria = session.createCriteria(Table1.class, "Table1");
criteria
.setProjection(
Projections.projectionList()
.add(Projections.property("Table1.col1"), "col1")
.add(Projections.property("Table1.col2"), "col2")
)
;
Entities (very short version)
#Entity
#Table(name = "Table1")
public class Table1 {
#Id
#Column(name = "uid")
public String getUid();
#Column(name = "col1")
public String getCol1();
#Column(name = "col2")
public String getCol2();
#Column(name = "col3")
public String getCol3();
#Column(name = "col4")
public String getCol4();
#Column(name = "col5")
public String getCol5();
}
#Entity
#Table(name = "Table2")
public class Table2 {
#Id
#Column(name = "uid")
public String getUid();
#Column(name = "col3")
public BigDecimal getCol3();
#Column(name = "col4")
public String getCol4();
#Column(name = "col5")
public String getCol5();
}
For a correlated subquery (like the one you presented above), you can use #Formula which can take an arbitrary SQL query. Then, you'll need to fetch the entity and the subquery will be executed.
However, a native SQL is more elegant if you only need this query for a single business requirement.
As for derived table queries (e.g. select from select), neither JPA nor Hibernate support derived table queries for a very good reason.
Entity queries (JPQL pr Criteria) are meant to fetch entities that you plan to modify.
For a derived table projection, native SQL is the way to go. Otherwise, why do you think EntityManager offers a createNativeQuery method?