Spring JPA Specification. Join query in order to find user - spring

I have two tables:
#Table(name = "userinstance")
#Entity
public class UserInstance implements Comparable<UserInstance> {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#NotNull
#Size(min = 1, max = 100)
#Column(length = 100, unique = false, nullable = false)
private String uuid;
#NotNull
#Size(min = 1, max = 100)
#Column(length = 100, unique = false, nullable = false)
String userId;
#NotNull
#Size(min = 1, max = 100)
#Column(length = 100, unique = false, nullable = false)
private String name;
#NotNull
#Size(min = 1, max = 2048)
#Column(length = 2048, unique = false, nullable = false)
private String description;
#JsonBackReference
#OneToMany(cascade = CascadeType.ALL, mappedBy = "userInstance")
private List<UserDetailsInstance> userDetailsInstances = new ArrayList<>();
}
#Table(name = "userdetailsinstance")
#Entity
public class UserDetailsInstance implements Comparable<UserDetailsInstance> {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#NotNull
#Size(min = 1, max = 100)
#Column(length = 100, unique = false, nullable = false)
private String uuid;
#JsonManagedReference
#ManyToOne
#JoinColumn(name = "usr_instance_id", nullable = false)
private UserInstance userinstance;
#NotNull
#Size(min = 1, max = 100)
#Column(length = 100, unique = false, nullable = false)
private String name;
#NotNull
#Size(min = 1, max = 100)
#Column(length = 100, unique = false, nullable = false)
private String type;
#Column(length = 1)
private Status status;
#Column(length = 1000)
private String message;
#Column(length = 4000)
private String instanceData;
}
What I want to achieve is to be able to find the User (UserInstance table) using filter and using only single query parameter ("name");
In order to do that i have a:
public class UserInstanceQuery extends BaseQuery {
private final Long id;
private final String name;
private final String user;
private final Instant createdDate;
private final Instant lastModifiedDate;
public UserInstanceQuery(Long id, String name, String user, String createdDate, String lastModifiedDate, Pageable pageable) {
super(pageable);
this.id = id;
this.name = name;
this.user = user;
this.createdDate = convertStringToInstant(createdDate);
this.lastModifiedDate = convertStringToInstant(lastModifiedDate);
}
public Specification<UserInstance> toSpecification() {
return new UserSpecification(this);
}
}
And the specification looks like:
public class UserInstanceSpecification implements Specification<UserInstance> {
private final UserInstanceQuery userInstanceQuery;
#Override
public Predicate toPredicate(Root<UserInstance> root, CriteriaQuery<?> query, CriteriaBuilder cb) {
List<Predicate> predicates = new LinkedList<>();
if (userInstanceQuery.getId() != null) {
predicates
.add(
cb.equal(root.get("id"),
userInstanceQuery.getId())
);
}
if (userInstanceQuery.getUser() != null && !userInstanceQuery.getUser().isEmpty()) {
predicates
.add(cb.equal(
cb.lower(root.get("userId")), userInstanceQuery.getUser().toLowerCase()));
}
if (userInstanceQuery.getName() != null && !userInstanceQuery.getName().isEmpty()) {
Subquery<UserInstance> subquery = query.subquery(UserInstance.class);
Root<UserDetailsInstance> subRoot = subquery.from(UserDetailsInstance.class);
List<Predicate> subPredicates = new ArrayList<>();
subPredicates.add(cb.like(subRoot.get("instanceData"), "%" + userInstanceQuery.getName() + "%"));
subquery.select(subRoot.get("userInstance")).where(subPredicates.toArray(new Predicate[predicates.size()]));
predicates.add(
cb.or(
cb.like(cb.lower(root.get("name")), "%" + userInstanceQuery.getName().toLowerCase() + "%"),
cb.like(cb.lower(root.get("environment")), "%" + userInstanceQuery.getName().toLowerCase() + "%"),
cb.like(cb.lower(root.get("description")), "%" + userInstanceQuery.getName().toLowerCase() + "%"),
cb.exists(subquery)
)
);
}
if (userInstanceQuery.getCreatedDate() != null && userInstanceQuery.getLastModifiedDate() != null) {
predicates.add(
cb.and(
cb.greaterThanOrEqualTo(root.get("createdDate"), userInstanceQuery.getCreatedDate()),
cb.lessThanOrEqualTo(root.get("lastModifiedDate"), userInstanceQuery.getLastModifiedDate())
)
);
}
return cb.and(predicates.toArray(new Predicate[predicates.size()]));
}
private boolean currentQueryIsCountRecords(CriteriaQuery<?> criteriaQuery) {
return criteriaQuery.getResultType() == Long.class || criteriaQuery.getResultType() == long.class;
}
}
The issue is, that when i search for a string that contains only in UserDetailsInstance "instanceData" Field i get multiple results that don't actually have the data i need and are not referring to the user
Hibernate generated an sql like this:
select user0_.id as id1_7_, user0_.created_by as created_by2_7_, user0_.created_date as created_date3_7_, user0_.last_modified_by as last_modified_by4_7_, user0_.last_modified_date as last_modified_date5_7_, user0_.version as version6_7_, user0_.description as description7_7_, user0_.environment as environment8_7_, user0_.name as name9_7_, user0_.user_id as user_id10_7_, user0_.uuid as uuid11_7_ from usertinstance user0_
where (lower(user0_.name) like ?
or lower(user0_.environment) like ?
or lower(user0_.description) like ?
or exists (select userdetail.tds_instance_id
from userdetailsinstance userdetail
cross join usertinstance user2_
where userdetail.tds_instance_id=user2_.id
and (userdetail.instance_data like ?)))
and user0_.created_date>=? and user0_.last_modified_date<=? order by user0_.id desc fetch first ? rows only
If i try to run this query in SQLDeveloper/Datagrip I also get a multiple set of rows
At the same time if i change (line 8)
where userdetail.usr_instance_id=user2_.id
to
where userdetail.usr_instance_id=user0_.id
I get the result i need
Can you please point me to the error in Query creation or maybe tell me how to make this query return a value i need
Thanks!

The solution was much easier as i've expected
UserInstanceSpecification:
if (userInstanceQuery.getName() != null && !userInstanceQuery.getName().isEmpty()) {
Join<UserInstance, UserDetailsInstance> bos = root.join("userDetailsInstances", JoinType.LEFT);
predicates.add(
cb.or(
cb.like(cb.lower(root.get("name")), "%" + userInstanceQuery.getName().toLowerCase() + "%"),
cb.like(cb.lower(root.get("description")), "%" + userInstanceQuery.getName().toLowerCase() + "%"),
cb.like(cb.lower(bos.get("instanceData")), "%" + userInstanceQuery.getName().toLowerCase() + "%")
)
);
}

Related

Spring specification with custom cross join by simple attribute

Following scenario
#Entity("YEAR")
public class Year{
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "ID", unique = true, nullable = false)
public Long id;
#Column(name = "NAME", nullable = false, length = 10)
public Long name;
...
}
#Entity("FOO")
public class Foo {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "ID", unique = true, nullable = false)
public Long id;
#Column(name = "FK_YEAR", nullable = false)
public Long yearId;
#Column(name = "NAME", nullable = false, length = 10)
public String name;
...
}
#Entity("FII")
public class Fii {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "ID", unique = true, nullable = false)
public Long id;
#Column(name = "FK_YEAR", nullable = false)
public Long yearId;
#Column(name = "CODE", nullable = false, length = 10)
public String code;
...
}
#Entity("NTOM")
public class NtoM {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "ID", unique = true, nullable = false)
public Long id;
#Column(name = "FK_FOO", nullable = false)
public Long fooId;
#Column(name = "FK_FII", nullable = false)
public Long fiiId;
#Column(name = "STATE", nullable = false)
public Boolean state;
#Column(name = "VALUES", length = 500)
public String values;
...
}
Resulting in an ERP like this:
I now do have a JpaRepository like this:
#Repository
public interface NtoMRepository extends JpaRepository<NtoM, Long>, JpaSpecificationExecutor<NtoM> {
String BASE_QUERY =
"SELECT"
// prevent jpa to return null instead of id=0
+ " CASE WHEN ntom.ID IS NULL THEN 0 ELSE ntom.ID END AS ID ,"
+ " CASE WHEN ntom.STATE IS NULL THEN 0 ELSE ntom.STATE END AS STATE ,"
+ " ntom.VALUES,"
+ " fii.ID AS FK_FII,"
+ " foo.ID AS FK_FOO "
+ " FROM MYSCHEMA.FOO foo"
+ " INNER JOIN MYSCHEMA.FII fii ON fii.FK_YEAR = foo.FK_YEAR"
+ " OUTER JOIN MYSCHEMA.NTOM ntom ON ntom.FK_FII = fii.ID AND ntom.FK_FOO = foo.ID"
#Query(value = BASE_QUERY + " WHERE fii.ID = :fiiId", nativeQuery = true)
List<Option> findByFiiId(#Param("fiiId") Long fiiId);
#Query(value = BASE_QUERY + " WHERE foo.ID = :fooId", nativeQuery = true)
List<Option> findByFooId(#Param("fooId") Long fooId);
}
So the 2 queries here compute me missing entries whenever I call them, which works out quite nicely.
How could I use the "toPredicate" of the https://spring.io/blog/2011/04/26/advanced-spring-data-jpa-specifications-and-querydsl/ to accomplish this by creating a similar behavior but with dynamic parameters?
I cant just use criteriabuilder "join" as the values are only "basic attributes". Also do I want to reuse the dynamic approach as the "controller endpoint can look like"
#GetMapping
public List<NtoM> find(#RequestParam(value = "id", required = false, defaultValue = "0") Long id,#RequestParam(value = "fiiId", required = false, defaultValue = "0") Long fiiId, #RequestParam(value = "fooId", required = false, defaultValue = "0") Long fooId){
Specification<NtoM> spec = ... //build as AND construct of all parameters (if not null or empty add it)
// TODO instead of the SELECT * FROM myschema.ntom the custom query here!
return repo.findAll(spec);
}
How can I do this. I can also use the EntityManager and the criteriaBuilder.createTupleQuery(). But it seems to not work (I cant join the tables as there is no "ManyToOne" between them)
Why aren't there relationships in your domain model? It can be handled efficiently if you change your model to have relationships. Here is how I would approach this problem:
Start with creating relationships.
#Entity("YEAR")
public class Year{
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "ID", unique = true, nullable = false)
public Long id;
#Column(name = "NAME", nullable = false, length = 10)
public Long name;
...
}
#Entity("FOO")
public class Foo {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "ID", unique = true, nullable = false)
public Long id;
//#Column(name = "FK_YEAR", nullable = false)
//public Long yearId;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "FK_YEAR", referencedColumnName = "ID")
public Year year;
#Column(name = "NAME", nullable = false, length = 10)
public String name;
...
}
#Entity("FII")
public class Fii {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "ID", unique = true, nullable = false)
public Long id;
//#Column(name = "FK_YEAR", nullable = false)
//public Long yearId;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "FK_YEAR", referencedColumnName = "ID")
public Year year;
#Column(name = "CODE", nullable = false, length = 10)
public String code;
...
}
#Entity("NTOM")
public class NtoM {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "ID", unique = true, nullable = false)
public Long id;
//#Column(name = "FK_FOO", nullable = false)
//public Long fooId;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "FK_FOO", referencedColumnName = "ID")
public Foo foo;
//#Column(name = "FK_FII", nullable = false)
//public Long fiiId;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "FK_FII", referencedColumnName = "ID")
public Fii fii;
#Column(name = "STATE", nullable = false)
public Boolean state;
#Column(name = "VALUES", length = 500)
public String values;
...
}
Change the controller to get the request parameters into map.
Then delegate the data retrieving logic to the service layer (You can also create custom repository impelementation).
#GetMapping
public List<NtoM> find(#RequestParam Map<String, String> requestParams) {
return service.findByRequestParams(requestParams);
}
In the service class
public List<NtoM> findByRequestParams(Map<String, String> requestParams) {
return repository.findAll(createSpec(requestParams));
}
private Specification<NtoM> createSpec(Map<String, String> requstParams ) {
return (root, query, criteriaBuilder) -> {
List<Predicate> predicates = new ArrayList<>();
Join<NtoM, Fii> firstJoin = root.join("fii", JoinType.INNER);
Join<NtoM, Foo> secondJoin = fiiJoin("foo", JoinType.LEFT);
String value = requstParams.get("id");
if(StringUtils.isNotBlank(value)) {
Predicate id = criteriaBuilder.equal(secondJoin.get("id"), Long.parseLong(value));
predicates.add(id);
}
value = requestParams.get("fiiId");
if(StringUtils.isNotBlank(value)) {
Predicate fii = criteriaBuilder.equal(secondJoin.get("fii"), Long.parseLong(value));
predicates.add(fii);
}
value = requestParams.get("fooId");
if(StringUtils.isNotBlank(value)) {
Predicate foo = criteriaBuilder.equal(secondJoin.get("foo"), Long.parseLong(value));
predicates.add(foo);
}
//Later you can add new options without breaking the existing API
// For example like search by values
value = requestParams.get("values");
if(StringUtils.isNotBlank(value)) {
Predicate likeValues = criteriaBuilder.like(secondJoin.get("values"), "%" + value + "%");
predicates.add(likeValues);
}
return criteriaBuilder.and(predicates.toArray(Predicate[]::new));
};
}

incorrectly joining 2 entities in spring data jpa

#Entity
#Table(name = "product_table")
public class Product {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "Product_id" , nullable = false, unique= true , length = 5)
private int ProductId;
#Column(name = "Product_Name" ,nullable = false , length = 50)
private String ProductName;
#Column(name = "Description" ,nullable = false , length = 200)
private String Description;
#Column(name ="Price" , nullable = false, unique= true , length = 5)
private Double Price;
#Column(name = "Discount" , nullable = false, unique= true , length = 5)
private Double Discount;
#Column(name ="Delivery_Charges" , nullable = false, unique= true , length = 5)
private Double DeliveryCharges;
#Column(name = "Avg_Rating",nullable = false, unique= true , length = 5)
private int AvgRating;
#Column(name = "Seller_Name",nullable = false, unique= true , length = 15)
private String SellerName;
#OneToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "Product_id", insertable = false, updatable = false)
#Fetch(FetchMode.JOIN)
private Cart Cart;
#OneToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "ProductId", insertable = false, updatable = false)
#Fetch(FetchMode.JOIN)
private WishList WishList;
public String getSellerName() {
return SellerName;
}
public void setSellerName(String sellerName) {
SellerName = sellerName;
}
public int getProductId() {
return ProductId;
}
public void setProductId(int productId) {
ProductId = productId;
}
public String getProductName() {
return ProductName;
}
public void setProductName(String productName) {
ProductName = productName;
}
public String getDescription() {
return Description;
}
public void setDescription(String description) {
Description = description;
}
public Double getPrice() {
return Price;
}
public void setPrice(Double price) {
Price = price;
}
public Double getDiscount() {
return Discount;
}
public void setDiscount(Double discount) {
Discount = discount;
}
public Double getDeliveryCharges() {
return DeliveryCharges;
}
public void setDeliveryCharges(Double deliveryCharges) {
DeliveryCharges = deliveryCharges;
}
public int getAvgRating() {
return AvgRating;
}
public void setAvgRating(int avgRating) {
AvgRating = avgRating;
}
}
#Entity
#Table(name = "Cart_table")
public class Cart {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "Cart_Id" , nullable = false, unique = true, length = 5)
private int CartId;
#Column(name = "Product_Name" , nullable = false, length = 50)
private String ProductName;
#Column(name = "Seller_Name" , nullable = false,length = 15)
private String SellerName;
#Column(name = "Quantity" , nullable = false)
private int Quantity;
#Column(name = "Cart_Offer_Price" , unique = true)
private Double CartOfferPrice;
#Column(name = "Product_id" , nullable = false, unique = true, length = 5)
private int ProductId;
public int getProductId() {
return ProductId;
}
public void setProductId(int productId) {
ProductId = productId;
}
#OneToOne(targetEntity = Product.class, mappedBy = "Cart", orphanRemoval = false, fetch = FetchType.LAZY)
private Product product;
#OneToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "Cart_Id", insertable = false, updatable = false)
#Fetch(FetchMode.JOIN)
private OrdersCartMapping OrdersCartMapping;
public int getCartId() {
return CartId;
}
public void setCartId(int cartId) {
CartId = cartId;
}
public String getProductName() {
return ProductName;
}
public void setProductName(String productName) {
ProductName = productName;
}
public String getSellerName() {
return SellerName;
}
public void setSellerName(String sellerName) {
SellerName = sellerName;
}
public int getQuantity() {
return Quantity;
}
public void setQuantity(int quantity) {
Quantity = quantity;
}
public Double getCartOfferPrice() {
return CartOfferPrice;
}
public void setCartOfferPrice(Double cartOfferPrice) {
CartOfferPrice = cartOfferPrice;
}
}
#Repository
public interface CartRepository extends JpaRepository<Cart , Integer> {
#Query("SELECT new com.megamartonline.dto.CartProductDto(c.ProductName,c.CartOfferPrice,c.Quantity , p.Price ,p.Discount,p.DeliveryCharges,c.CartId ) "
+ "FROM Cart c INNER JOIN c.product p")
List<CartProductDto> fetchProductCartDataInnerJoin();
}
Here i m trying to join Product with Cart using Product_id column but when i test my repository method it is incorrectly joining as below
select
cart0_.product_name as col_0_0_,
cart0_.cart_offer_price as col_1_0_,
cart0_.quantity as col_2_0_,
product1_.price as col_3_0_,
product1_.discount as col_4_0_,
product1_.delivery_charges as col_5_0_
from
cart_table cart0_
inner join
product_table product1_
on cart0_.cart_id=product1_.product_id
please help , what i am doing wrong here.
Here i m trying to join 2 Product with Cart using Product_id column but when i test my repository method it is incorrectly joining.
In Product, instead of productId, you have to declare the join to JPA in the entity this way:
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "Product_id")
private Product Product;
Hibernate now knows how to handle the join.
You can't use both an ID column and a JoinColumn; it won't know which one to use, so you should remove productId.
I suggest a #ManyToOne because the #OneToOne join you are using seems strange for a cart. Usually a cart has multiple (many) products.
You can easily change the fetch type from lazy to eager

Why hibernate is throwing constraintViolationException?

Order Entity
#Entity
#Table(name = "Order",
indexes = {
#Index(name = "ORDER_X1", columnList = "REFERENCE_ID,SOURCE_ID"),
#Index(name = "ORDER_X2", columnList = "TYPE,STATUS")
}
)
#DiscriminatorColumn(name="PROCESSOR_TYPE", discriminatorType=DiscriminatorType.STRING, length = 80)
#SequenceGenerator(name="orderSeq", sequenceName="ORDER_SEQ")
#Inheritance(strategy= InheritanceType.JOINED)
public abstract class OrderEntity implements Serializable {
#Id
#GeneratedValue(strategy= GenerationType.SEQUENCE, generator="orderSeq")
private Long id;
#ManyToMany(cascade={CascadeType.MERGE})
#JoinTable(
name = "FILE_ORDER_MAP",
joinColumns = {#JoinColumn(name = "ORDER_ID")},
inverseJoinColumns = {#JoinColumn(name = "FILE_ID")}
)
private Set<TransferFile> transferFiles = new HashSet<>();
#Column(name = "TYPE")
#Enumerated(EnumType.STRING)
private OrderType type;
#Column(name = "AMOUNT", precision = 12, scale = 2)
private LcMoney amount;
#Column(name = "STATUS")
#Enumerated(EnumType.STRING)
private OrderStatus reconStatus;
#Type(type = LcUtc.JPA_JODA_TIME_TYPE)
#Column(name = "STATUS_D", nullable = false)
#LcDateTimeUtc()
private DateTime reconStatusDate;
#Column(name = "REFERENCE_ID")
private Long referenceId;
#Column(name = "SOURCE_ID")
private Long sourceId;
#Column(name = "ACCOUNT_ID")
private Long accountId;
#Column(name = "PROCESSOR_TYPE", insertable = false, updatable = false)
#Enumerated(EnumType.STRING)
private OrderProcessorType processorType;
#Type(type = LcUtc.JPA_JODA_TIME_TYPE)
#Column(name = "TX_EXECUTION_D")
#LcDateTimeUtc()
private DateTime executedDate;
#Type(type = LcUtc.JPA_JODA_TIME_TYPE)
#Column(name = "CREATE_D")
#LcDateTimeUtc()
private DateTime createDate;
#Column(name = "IS_ON_DEMAND")
#Type(type = "yes_no")
private boolean isOnDemand;
#ManyToOne(fetch = FetchType.LAZY, optional = true, cascade = {CascadeType.PERSIST})
#JoinColumn(name="PAYER_ID", nullable=true)
private Payer payer;
#OneToMany(cascade = CascadeType.ALL)
#JoinColumn(name = "ORDER_ID", referencedColumnName = "ID")
private List<OrderTransaction> orderTransactions;
#OneToMany(cascade = {CascadeType.ALL})
#JoinColumn(name = "ORDER_ID", referencedColumnName = "ID",
foreignKey = #ForeignKey(name = "FK_ORDER")
)
private List<MatchResult> matchResults;
#Version
#Column(name = "VERSION")
private Integer version;
#Embedded
#AttributeOverrides({
#AttributeOverride(name = "externalSourceId", column = #Column(name = "TRANS_EXT_SRC_ID")),
#AttributeOverride(name = "externalId", column = #Column(name = "TRANS_EXT_REF_ID"))
})
private LcExternalIdEntity transExtId;
#PreUpdate
#PrePersist
public void beforePersist() {
if (reconStatusDate != null) {
reconStatusDate = reconStatusDate.withZone(DateTimeZone.UTC);
}
if (executedDate != null) {
executedDate = executedDate.withZone(DateTimeZone.UTC);
}
if (createDate != null) {
createDate = createDate.withZone(DateTimeZone.UTC);
}
}
// getters and setters
}
//controller method
public Response processFile(){
// separate trasaction
service.readFileAndCreateOrders(); // read files and create orders in new status
List<Order> newOrders = service.getNewOrders();
for( Order order: newOrders){
service.processOrder(order); // separate transaction
}
}
#Transaction
void processOrder(OrderEntity order){
matchResultJpaRepository.save(orderEntity.id);
log.info("Saving matchId={} for order={}", match.getId(), order.getId());
// create new transaction and add to order
OrderTransaction transaction = createNewTransaction(order);
order.getTransactions().add(transaction);
order.setStatus("PROCESSED");
log.info("Saving Order id={}, Type={}, Status={} ", order.getId(), order.getType(), order.getStatus());
orderRepository.save(order);
}
I am seeing this below error.
ORA-01407: cannot update ("PAYMENTS"."MATCH_RESULT"."ORDER_ID") to NULL
This endpoing is not exposed to user. There is a batch job which invokes this endpoint.
This code has been there for atleast a year and this is the first time i am seeing this.
This happened only once and for only one call. I am seeing both the logs printed. I am puzzled why I am seeing above error complaining about NULL order id. From the logs, we can confirm that the order id is definitely not null.
Any idea why this is happening? What can be done to fix this?

Spring Data JPA Pageable with named entity graph

I am using Spring data with JPA 2.1 to retrieve the entities with pagination & specification (criteria builder) using below method:
Page<T> findAll(Specification<T> spec, Pageable pageable)
How can i specify #EntityGraph on this method? Rightnow, if i annotate with #EntityGraph, Spring stops doing the pagination and fetch whole data in one go.
#EqualsAndHashCode(callSuper = true)
#Data
#Entity
#Table(name = "cd_order")
#Where(clause = "deleted = " + UN_DELETED)
#NamedEntityGraphs(value = {
#NamedEntityGraph(name = "findAll", attributeNodes =
{#NamedAttributeNode(value = "company"),
#NamedAttributeNode(value = "partnerCompany"),
// remove one to many , #NamedAttributeNode(value = "orderOssUrls"),
#NamedAttributeNode(value = "patient")
})}
)
public class Order extends BaseEntity {
#Column(name = "patient_id", insertable = false, updatable = false, columnDefinition = " BIGINT NOT NULL COMMENT '订单id' ")
private Long patientId;
#OneToOne(fetch = FetchType.EAGER)
#Fetch(FetchMode.JOIN)
#NotFound(action = NotFoundAction.IGNORE)
#JoinColumn(foreignKey = #ForeignKey(ConstraintMode.NO_CONSTRAINT), name = "patient_id", referencedColumnName = "id")
private Patient patient;
#Column(name = "patient_name", columnDefinition = " VARCHAR(48) COMMENT '案例编号' ")
private String patientName;
#Column(name = "case_code", columnDefinition = " VARCHAR(48) NOT NULL COMMENT '案例编号' ")
private String caseCode;
#Column(name = "company_id", insertable = false, updatable = false, columnDefinition = " BIGINT COMMENT 'companyId' ")
private Long companyId;
#OneToOne(fetch = FetchType.EAGER)
#Fetch(FetchMode.JOIN)
#JoinColumn(foreignKey = #ForeignKey(ConstraintMode.NO_CONSTRAINT), name = "company_id", referencedColumnName = "id")
#NotFound(action = NotFoundAction.IGNORE)
private Company company;
#Column(name = "partner_company_id", insertable = false, updatable = false, columnDefinition = " BIGINT COMMENT '合作伙伴的companyId' ")
private Long partnerCompanyId;
#OneToOne(fetch = FetchType.EAGER)
#Fetch(FetchMode.JOIN)
#JoinColumn(foreignKey = #ForeignKey(ConstraintMode.NO_CONSTRAINT), name = "partner_company_id", referencedColumnName = "id")
#NotFound(action = NotFoundAction.IGNORE)
private Company partnerCompany;
#Column(name = "order_id", columnDefinition = " BIGINT NOT NULL COMMENT '订单id' ")
private Long orderId;
#Fetch(FetchMode.JOIN)
#NotFound(action = NotFoundAction.IGNORE)
#JoinColumn(foreignKey = #ForeignKey(ConstraintMode.NO_CONSTRAINT), name = "order_id", referencedColumnName = "order_id")
#OneToMany(fetch = FetchType.LAZY)
private List<OrderOssUrl> orderOssUrls;
#Column(name = "recovery_info", columnDefinition = " VARCHAR(128) COMMENT '恢复信息' ")
private String recoveryInfo;
/**
* 交货日期
*/
#JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd HH:mm:ss")
#Column(name = "submit_time", columnDefinition = " datetime COMMENT '请求交货日期' ")
private LocalDateTime submitTime;
/**
* 订购日期
*/
#JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd HH:mm:ss")
#Column(name = "order_time", columnDefinition = " datetime COMMENT '订购日期' ")
private LocalDateTime orderTime;
/**
* 扫描日期
*/
#JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd HH:mm:ss")
#Column(name = "scan_time", columnDefinition = " datetime COMMENT '扫描日期' ")
private LocalDateTime scanTime;
#Enumerated(value = EnumType.STRING)
#Column(name = "order_type", columnDefinition = " VARCHAR(20) NOT NULL COMMENT '订单类型' ")
private OrderTypeEnum orderType;
#Enumerated(value = EnumType.STRING)
#Column(name = "cc_order_status", columnDefinition = " VARCHAR(20) COMMENT '诊所订单状态' ")
private OrderStatusEnum ccOrderStatus;
#Enumerated(value = EnumType.STRING)
#Column(name = "lab_order_status", columnDefinition = " VARCHAR(20) COMMENT '工厂订单状态' ")
private OrderStatusEnum labOrderStatus;
/**
* #param orderType 订单类型
*/
public void init(#NonNull OrderTypeEnum orderType) {
generateCaseCode();
if (orderType == OrderTypeEnum.CLINIC) {
this.setCcOrderStatus(OrderStatusEnum.NEW);
} else {
this.setLabOrderStatus(OrderStatusEnum.NEW);
}
this.setOrderType(orderType);
this.setOrderId(GlobalUniqueIdGeneratorUtils.generateOrderId());
}
/**
* 设置案例编号
*/
private void generateCaseCode() {
this.setCaseCode(getYMDStrCurrently() + "-" + this.getPatient().getName());
}
}
public interface OrderRepository extends BaseRepository<Order, Long> {
#NonNull
#EntityGraph(value = "findAll", type = EntityGraph.EntityGraphType.LOAD)
Page<Order> findAll(Specification<Order> specification, #NonNull Pageable pageable);
}
sql
select order0_.id as id1_5_0_,
patient1_.id as id1_9_1_,
company2_.id as id1_1_2_,
company3_.id as id1_1_3_,
order0_.create_time as create_t2_5_0_,
order0_.deleted as deleted3_5_0_,
order0_.update_time as update_t4_5_0_,
order0_.version as version5_5_0_,
order0_.case_code as case_cod6_5_0_,
order0_.cc_order_status as cc_order7_5_0_,
order0_.company_id as company_8_5_0_,
order0_.lab_order_status as lab_orde9_5_0_,
order0_.order_id as order_i10_5_0_,
order0_.order_time as order_t11_5_0_,
order0_.order_type as order_t12_5_0_,
order0_.partner_company_id as partner13_5_0_,
order0_.patient_id as patient14_5_0_,
order0_.patient_name as patient15_5_0_,
order0_.recovery_info as recover16_5_0_,
order0_.scan_time as scan_ti17_5_0_,
order0_.submit_time as submit_18_5_0_,
patient1_.create_time as create_t2_9_1_,
patient1_.deleted as deleted3_9_1_,
patient1_.update_time as update_t4_9_1_,
patient1_.version as version5_9_1_,
patient1_.activity_time as activity6_9_1_,
patient1_.day as day7_9_1_,
patient1_.email as email8_9_1_,
patient1_.month as month9_9_1_,
patient1_.name as name10_9_1_,
patient1_.note as note11_9_1_,
patient1_.phone as phone12_9_1_,
patient1_.sex as sex13_9_1_,
patient1_.year as year14_9_1_,
company2_.create_time as create_t2_1_2_,
company2_.deleted as deleted3_1_2_,
company2_.update_time as update_t4_1_2_,
company2_.version as version5_1_2_,
company2_.additional_info_id as addition6_1_2_,
company2_.address as address7_1_2_,
company2_.biz_type as biz_type8_1_2_,
company2_.cell_phone as cell_pho9_1_2_,
company2_.city as city10_1_2_,
company2_.country_code as country11_1_2_,
company2_.country_name as country12_1_2_,
company2_.description as descrip13_1_2_,
company2_.icon as icon14_1_2_,
company2_.language as languag15_1_2_,
company2_.mechanism_id as mechani16_1_2_,
company2_.name as name17_1_2_,
company2_.office_phone as office_18_1_2_,
company2_.postcode as postcod19_1_2_,
company2_.province as provinc20_1_2_,
company2_.time_zone as time_zo21_1_2_,
company3_.create_time as create_t2_1_3_,
company3_.deleted as deleted3_1_3_,
company3_.update_time as update_t4_1_3_,
company3_.version as version5_1_3_,
company3_.additional_info_id as addition6_1_3_,
company3_.address as address7_1_3_,
company3_.biz_type as biz_type8_1_3_,
company3_.cell_phone as cell_pho9_1_3_,
company3_.city as city10_1_3_,
company3_.country_code as country11_1_3_,
company3_.country_name as country12_1_3_,
company3_.description as descrip13_1_3_,
company3_.icon as icon14_1_3_,
company3_.language as languag15_1_3_,
company3_.mechanism_id as mechani16_1_3_,
company3_.name as name17_1_3_,
company3_.office_phone as office_18_1_3_,
company3_.postcode as postcod19_1_3_,
company3_.province as provinc20_1_3_,
company3_.time_zone as time_zo21_1_3_
from cd_order order0_
left outer join cd_patient patient1_ on order0_.patient_id = patient1_.id
left outer join cd_company company2_ on order0_.partner_company_id = company2_.id
left outer join cd_company company3_ on order0_.company_id = company3_.id
where (order0_.deleted = 0)
and order0_.order_id >= 1
and (order0_.case_code like '%case%')
and patient1_.year >= 1
and patient1_.month >= 1
and patient1_.day >= 1
and patient1_.sex = 'MALE'
and (patient1_.phone like '%phone%')
and (patient1_.name like '%name%')
and (order0_.update_time between '2021-06-23 14:40:53.101831' and '2021-06-27 14:40:53.101944')
order by order0_.create_time asc, order0_.submit_time desc
limit 10
please remove #NamedAttributeNode of on to many enter image description here,and try debug org.hibernate.hql.internal.ast.QueryTranslatorImplenter image description here
。 please care RowSelection。 i fixed the 'bug'

Spring data JPA entity change not being persisted

I have a Spring data entity (using JPA w/ Hibernate and MySQL) defined as such:
#Entity
#Table(name = "dataset")
public class Dataset {
#Id
#GenericGenerator(name = "generator", strategy = "increment")
#GeneratedValue(generator = "generator")
private Long id;
#Column(name = "name", nullable = false)
private String name;
#Column(name = "guid", nullable = false)
private String guid;
#Column(name = "size", nullable = false)
private Long size;
#Column(name = "create_time", nullable = false)
private Date createTime;
#OneToOne(optional = false)
#JoinColumn(name = "created_by")
private User createdBy;
#Column(name = "active", nullable = false)
private boolean active;
#Column(name = "orig_source", nullable = false)
private String origSource;
#Column(name = "orig_source_type", nullable = false)
private String origSourceType;
#Column(name = "orig_source_org", nullable = false)
private String origSourceOrg;
#Column(name = "uri", nullable = false)
private String uri;
#Column(name = "mimetype", nullable = false)
private String mimetype;
#Column(name = "registration_state", nullable = false)
private int registrationState;
#OneToMany(fetch = FetchType.EAGER, cascade = {CascadeType.ALL})
#JoinColumn(name = "dataset_id")
#JsonManagedReference
private List<DatasetFile> datasetFiles;
I have the following repository for this entity:
public interface DatasetRepo extends JpaRepository<Dataset, Long> {
#Query("SELECT CASE WHEN COUNT(p) > 0 THEN 'true' ELSE 'false' END FROM Dataset p WHERE p.uri = ?1 and p.registrationState>0")
public Boolean existsByURI(String location);
#Query("SELECT a FROM Dataset a LEFT JOIN FETCH a.datasetFiles c where a.registrationState>0")
public List<Dataset> getAll(Pageable pageable);
#Query("SELECT a FROM Dataset a LEFT JOIN FETCH a.datasetFiles c WHERE a.registrationState>0")
public List<Dataset> findAll();
#Query("SELECT a FROM Dataset a LEFT JOIN FETCH a.datasetFiles c where a.guid= ?1")
public Dataset findByGuid(String guid);
}
Now - In a controller, I am fetching a dataset, updating one of its attributes and I would be expecting that attribute change to be flushed to the DB, but it never is.
#RequestMapping(value = "/storeDataset", method = RequestMethod.GET)
public #ResponseBody
WebServiceReturn storeDataset(
#RequestParam(value = "dsGUID", required = true) String datasetGUID,
#RequestParam(value = "stType", required = true) String stType) {
WebServiceReturn wsr = null;
logger.info("stType: '" + stType + "'");
if (!stType.equals("MongoDB") && !stType.equals("Hive") && !stType.equals("HDFS")) {
wsr = getFatalWebServiceReturn("Invalid Storage type '" + stType + "'");
} else if (stType.equals("MongoDB")) {
/* Here is where I'm reading entity from Repository */
Dataset dataset = datasetRepo.findByGuid(datasetGUID);
if (dataset != null) {
MongoLoader mongoLoader = new MongoLoader();
boolean success = mongoLoader.loadMongoDB(dataset);
logger.info("Success: " + success);
if (success) {
/* Here is where I update entity attribute value, this is never flushed to DB */
dataset.setRegistrationState(1);
}
wsr = getWebServiceReturn(success ? 0 : -1, "Successfully loaded dataset files into " + stType + " storage", "Failed to load dataset files into " + stType + " storage");
}
}
return wsr;
}
Thank you
You need to annotate the method of request mapping with #Transactional.
Why? If you want to modify an object in memory and then it is updated transparently in the database you need do it inside an active transaction.
Don't forget you're using JPA (spring-data is using JPA) and if you want your Entity will be in a managed state you need an active transaction.
See:
http://www.objectdb.com/java/jpa/persistence/update
Transparent Update Once an entity object is retrieved from the
database (no matter which way) it can simply be modified in memory
from inside an active transaction:
Employee employee = em.find(Employee.class, 1);
em.getTransaction().begin();
employee.setNickname("Joe the Plumber");
em.getTransaction().commit();

Resources