Spring Data JPA Pageable with named entity graph - spring

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'

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));
};
}

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?

How to retrieve data based on inverseColumn data using CrudRepository in springboot?

I have two tables i.e. users and events. Users table will be filled when new user will sign up. Later same user can create calendar events. so events table will be filled and users_events will keep mapping of events based on user.
I would like to find all events based on logged in userId. so here is query, it should return data based on it.
select * from events where eventid in (select eventId from users_event where id_user=x ). Here is my Users and Event Entity class.
User.java
#Entity
#Table(name = "users")
public class User {
#Id
#GenericGenerator(name = "generator", strategy = "increment")
#GeneratedValue(generator = "generator")
#Column(name = "id", nullable = false)
private Long id;
#Column(name = "first_name", nullable = false)
private String firstName;
#Column(name = "family_name", nullable = false)
private String familyName;
#Column(name = "e_mail", nullable = false)
private String email;
#Column(name = "phone", nullable = false)
private String phone;
#Column(name = "language", nullable = false)
private String language;
#Column(name = "id_picture")
private String pictureId;
#Column(name = "login", nullable = false)
private String login;
#Column(name = "password", nullable = false)
private String password;
#Column(name = "birth_date")
private Date birthDate;
#Column(name = "enabled")
private Boolean enabled;
//getter and setter
Event.java
#Entity
#Table(name = "events")
public class Event {
#Id
#GenericGenerator(name = "generator", strategy = "increment")
#GeneratedValue(generator = "generator")
#Column(name = "eventId", nullable = false)
private Long eventId;
#Column(name = "title", nullable = false)
private String title;
#Column(name = "description", nullable = true)
private String description;
#Column(name = "startAt", nullable = false)
#Temporal(TemporalType.TIMESTAMP)
private Date startAt;
#Column(name = "endAt", nullable = true)
#Temporal(TemporalType.TIMESTAMP)
private Date endAt;
#Column(name = "isFullDay", nullable = false)
private Boolean isFullDay;
#ManyToMany(fetch = FetchType.EAGER)
#JoinTable(name = "users_event", joinColumns = { #JoinColumn(name = "id_event", referencedColumnName = "eventId") }, inverseJoinColumns = { #JoinColumn(name = "id_user", table = "users", referencedColumnName = "id") })
private Set<User> user = new HashSet<User>();
/getter and setter
EventRepo.java
public interface EventRepo extends CrudRepository<Event, Long> {
Event findByUser(Set<User> user);
}
I am trying to implement something, which can give me output of this query.
select * from events where eventid in (select eventId from users_event where id_user=x )
here is my implementation.any input please?
#RequestMapping(value = "/events", method = RequestMethod.GET)
public #ResponseBody List<Event> getEvents() {
logger.debug("get event list");
User x=new User();
x.setId(1);
Set<User> user= new HashSet();
user.add(x);
return (List<Event>) eventRepo.findByUser(user);
}
Just add a following method to your EventRepo:
List<Event> findAllByUserId(Long userId);
And modify your controller to something like this:
#RequestMapping(value = "/events", method = RequestMethod.GET)
public List<Event> getEvents() {
return eventRepo.findAllByUserId(1L);
}

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();

Jpa - Hibernate ManyToMany do many insert into join table

I have follows ManyToMany relationship between WorkDay(has annotation ManyToMany) and Event
WorkDay entity
#Entity
#Table(name = "WORK_DAY", uniqueConstraints = { #UniqueConstraint(columnNames = { "WORKER_ID", "DAY_ID" }) })
#NamedQueries({
#NamedQuery(name = WorkDay.GET_WORK_DAYS_BY_MONTH, query = "select wt from WorkDay wt where wt.worker = :worker and to_char(wt.day.day, 'yyyyMM') = :month) order by wt.day"),
#NamedQuery(name = WorkDay.GET_WORK_DAY, query = "select wt from WorkDay wt where wt.worker = :worker and wt.day = :day") })
public class WorkDay extends SuperClass {
private static final long serialVersionUID = 1L;
public static final String GET_WORK_DAYS_BY_MONTH = "WorkTimeDAO.getWorkDaysByMonth";
public static final String GET_WORK_DAY = "WorkTimeDAO.getWorkDay";
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "WORKER_ID", nullable = false)
private Worker worker;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "DAY_ID", nullable = false)
private Day day;
#Column(name = "COMING_TIME")
#Convert(converter = LocalDateTimeAttributeConverter.class)
private LocalDateTime comingTime;
#Column(name = "OUT_TIME")
#Convert(converter = LocalDateTimeAttributeConverter.class)
private LocalDateTime outTime;
#Enumerated(EnumType.STRING)
#Column(name = "STATE", length = 16, nullable = false)
private WorkDayState state = WorkDayState.NO_WORK;
#ManyToMany(fetch = FetchType.LAZY, cascade = CascadeType.ALL)
#JoinTable(name = "WORK_DAY_EVENT", joinColumns = {
#JoinColumn(name = "WORK_DAY_ID", nullable = false)}, inverseJoinColumns = {
#JoinColumn(name = "EVENT_ID", nullable = false)})
#OrderBy(value = "startTime desc")
private List<Event> events = new ArrayList<>();
protected WorkDay() {
}
public WorkDay(Worker worker, Day day) {
this.worker = worker;
this.day = day;
this.state = WorkDayState.NO_WORK;
}
}
Event entity
#Entity
#Table(name = "EVENT")
public class Event extends SuperClass {
#Column(name = "DAY", nullable = false)
#Convert(converter = LocalDateAttributeConverter.class)
private LocalDate day;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "TYPE_ID", nullable = false)
private EventType type;
#Column(name = "TITLE", nullable = false, length = 128)
private String title;
#Column(name = "DESCRIPTION", nullable = true, length = 512)
private String description;
#Column(name = "START_TIME", nullable = false)
#Convert(converter = LocalDateTimeAttributeConverter.class)
private LocalDateTime startTime;
#Column(name = "END_TIME", nullable = true)
#Convert(converter = LocalDateTimeAttributeConverter.class)
private LocalDateTime endTime;
#Enumerated(EnumType.STRING)
#Column(name = "STATE", nullable = false, length = 16)
private EventState state;
protected Event() {
}
}
Attached UI form for clarity
When I push Clock with run icon first time, it means "create event and start work day" in bean, calling the following methods:
public void startEvent() {
stopLastActiveEvent();
Event creationEvent = new Event(workDay.getDay().getDay(), selectedEventType, selectedEventType.getTitle(),
LocalDateTime.now());
String addEventMessage = workDay.addEvent(creationEvent);
if (Objects.equals(addEventMessage, "")) {
em.persist(creationEvent);
if (workDay.isNoWork()
&& !creationEvent.getType().getCategory().equals(EventCategory.NOT_INFLUENCE_ON_WORKED_TIME)) {
startWork();
}
em.merge(workDay);
} else {
Notification.warn("Невозможно создать событие", addEventMessage);
}
cleanAfterCreation();
}
public String addEvent(Event additionEvent) {
if (!additionEvent.getType().getCategory().equals(NOT_INFLUENCE_ON_WORKED_TIME)
&& isPossibleTimeBoundaryForEvent(additionEvent.getStartTime(), additionEvent.getEndTime())) {
events.add(additionEvent);
changeTimeBy(additionEvent);
} else {
return "Пересечение временых интервалов у событий";
}
Collections.sort(events, new EventComparator());
return "";
}
private void startWork() {
workDay.setComingTime(workDay.getLastWorkEvent().getStartTime());
workDay.setState(WorkDayState.WORKING);
}
In log I see:
insert into event table
update work_day table
insert into work_day_event table
on UI updated only attached frame. Always looks fine.. current WorkDay object have one element in the events collection, also all data is inserted into DB.. but if this time edit event row
event row listener:
public void onRowEdit(RowEditEvent event) {
Event editableEvent = (Event) event.getObject();
LocalDateTime startTime = fixDate(editableEvent.getStartTime(), editableEvent.getDay());
LocalDateTime endTime = fixDate(editableEvent.getEndTime(), editableEvent.getDay());
if (editableEvent.getState().equals(END) && startTime.isAfter(endTime)) {
Notification.warn("Невозможно сохранить изменения", "Время окончания события больше времени начала");
refreshEvent(editableEvent);
return;
}
if (workDay.isPossibleTimeBoundaryForEvent(startTime, endTime)) {
editableEvent.setStartTime(startTime);
editableEvent.setEndTime(endTime);
workDay.changeTimeBy(editableEvent);
em.merge(workDay);
em.merge(editableEvent);
} else {
refreshEvent(editableEvent);
Notification.warn("Невозможно сохранить изменения", "Пересечение временых интервалов у событий");
}
}
to the work_day_event insert new row with same work_day_id and event_id data. And if edit row else do one more insert and etc.. In the result I have several equals rows in work_day_event table. Why does this happen?
link to github project repository(look ver-1.1.0-many-to-many-problem branch)
Change CascadeType.ALL to CascadeType.MERGE for events in the WorkDay entity
Use this code
#ManyToMany(fetch = FetchType.LAZY, cascade = CascadeType.MERGE)
instead of
#ManyToMany(fetch = FetchType.LAZY, cascade = CascadeType.ALL)
Do not use ArrayList, use HashSet. Because ArrayList allows duplicates.
For more info about CasecadeType, follow the tutorial:
Hibernate JPA Cascade Types
Cascading best practices
I think the simple solution is to remove the cascade on many to many relationship and do the job manually ! . I see you already doing it redundantly anyway . So try removing you CascadeType.ALL
#ManyToMany(fetch = FetchType.LAZY, cascade = CascadeType.ALL)
How to persist #ManyToMany relation - duplicate entry or detached entity

Resources