#ManyToMany with Hibernate and JPA - java

I have two tables that are bound by a ManyToMany relationship.
Table 1 is TimeSlot and has a collection of documents created in that time slot.
Table 2 is Documents and has a collection of TimeSlots as a given document could be modified many times in different timeslots.
In this particular program for each document I find or create a document row for it. Further I find or create a time slot that represents the epoch of the action, create, update, delete. Note: delete is an event performed on the document, not on the row that represents it. In this program there are no deletes or removes performed against any row of either table.
However I am seeing as many deletes against the timeslot_document mapping table as there are inserts.
My question is this, why is Hibernate issuing deletes? It appears to be related to dirty update processing. By why repeated deletes from timeslot where id=1?
When I had OneToMany I didn't see the deletes but the model was inaccurate then (as you might imagine).
For every document I determine the date (hour accuracy only) that the action occured on, increment a tally and add the document to the collection.
Something else curious is I implement MVCC via the #Version annotation yet in a 'show innodb status\G' I see the table locked. At this moment there is one row for timeslot, 1988 documents tallied in it and the version of the row is at 1999. The table locking is concerning me.
Here is the main code fragment:
for (Eventlog event : eventsList) {
currentEvent = event.getEventId();
String tmp = formatter.format(event.getEpoch());
Date epoch = null;
try {
epoch = formatter.parse(tmp);
} catch (ParseException ex) {
Logger.getLogger(Converter.class.getName()).log(Level.SEVERE, null, ex);
}
cal.setTime(epoch);
Tuple tuple = holding.get(epoch);
if (tuple == null) {
tuple = new Tuple();
holding.put(epoch, tuple);
}
queryTimeSlot.setParameter("docType", docType);
queryTimeSlot.setParameter("date", epoch);
try {
ats = (TimeSlot)queryTimeSlot.getSingleResult();
insertATS = false;
} catch (Exception e) {
insertATS = true;
ats = new TimeSlot();
atsk = new TimeSlotKey();
atsk.setSlotDate(epoch);
atsk.setDocType(docType);
ats.setHour(cal.get(Calendar.HOUR_OF_DAY));
ats.setSlotKey(atsk);
}
if (processMM) {
queryDocument.setParameter("id", event.getUpdEventLogDocId());
queryDocument.setParameter("doc", docType);
try {
doc = (Document)queryDocument.getSingleResult();
insertDoc = false;
} catch (Exception e) {
doc = new Document();
docKey = new DocumentKey();
docKey.setDocid(event.getUpdEventLogDocId());
docKey.setType(docType);
doc.setDocKey(docKey);
insertDoc = true;
}
}
try {
ats.getDocuments().add(doc);
} catch (Exception e) {
Logger.getLogger(Converter.class.getName()).log(Level.SEVERE, "{0}", e);
System.exit(-1);
}
if (processMM) {
doc.getTimes().add(ats);
}
TimeSlot Entity
#Entity
#Table(name = "TimeSlot", catalog = "Analytics")
public class TimeSlot implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue
#Basic(optional=false)
#Column(name="id")
private Integer id;
#Basic(optional = false)
#Column(name = "slotKey")
private TimeSlotKey slotKey;
#Basic(optional = false)
#Column(name = "slotHour")
private int slotHour;
#Basic(optional = false)
#Column(name = "inserts")
private int inserts;
#Basic(optional = false)
#Column(name = "deletes")
private int deletes;
#Basic(optional = false)
#Column(name = "active")
private int active;
#ManyToMany(fetch=FetchType.LAZY)
#JoinColumn(name="doc_id")
private Collection<Document> documents;
#Basic(optional = false)
#Version
#Column(name = "version")
private int version;
public TimeSlot() {
documents = new ArrayList<Document>(0);
}
public Collection<Document> getDocuments() {
return documents;
}
public void setDocuments(Collection<Document>documents) {
this.documents = documents;
}
public int getHour() {
return slotHour;
}
public void setHour(int slotHour) {
this.slotHour = slotHour;
}
#Override
public int hashCode() {
int hash = 0;
hash += (slotKey != null ? slotKey.hashCode() : 0);
return hash;
}
#Override
public boolean equals(Object object) {
if (!(object instanceof ActivityTimeSlot)) {
return false;
}
}
Documents follows:
#Entity
#Table(name = "Documents", catalog = "Analytics")
public class Document implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue
#Basic(optional=false)
#Column(name="id")
private Integer id;
#Basic(optional = false)
#Column(name = "docKey")
private DocumentKey docKey;
#Basic(optional = false)
#Column(name = "inserts")
private int inserts;
#Basic(optional = false)
#Column(name = "deletes")
private int deletes;
#ManyToMany(fetch=FetchType.LAZY, mappedBy="")
#JoinColumn(name="slot_id")
private Collection<TimeSlot> times;
#Basic(optional = false)
#Version
#Column(name = "version")
private int version;
public Document() {
times = new ArrayList<TimeSlot>(0);
inserts = 0;
deletes = 0;
}
#Override
public int hashCode() {
return docKey.hashCode();
}
#Override
public boolean equals(Object object) {
return docKey.equals(object);
}
public int getDeletes() {
return deletes;
}
public void setDeletes(int deletes) {
this.deletes = deletes;
}
public void incrDeletes() {
deletes++;
}
public void incrInserts() {
inserts++;
}
public int getInserts() {
return inserts;
}
public void setInserts(int inserts) {
this.inserts = inserts;
}
public Pair getPair() {
return new Pair(inserts, deletes);
}
#Override
public String toString() {
return docKey.toString();
}
public Collection<ActivityTimeSlot> getTimes() {
return times;
}
public void setTimes(Collection<ActivityTimeSlot>times) {
this.times = times;
}
public DocumentKey getDocKey() {
return docKey;
}
public void setDocKey(DocumentKey docKey) {
this.docKey = docKey;
}
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
}
New info
I happened across this
ManyToMany assoicate delete join table entry
which is almost identical. It's just I don't understand Arthur's answer. Also I noticed in the mysql innodb status it is doing an outer left join...is that really necessary?
So in EntityA I should have a method add(EntityB) and in EntityB I should have a method called add(EntityA) in these methods I
public void add(EntityB entity) {
entity.getList().add(this);
getList().add(entity);
}
I am not seeing how this is functionally different than what I have.

Related

Problem in updating the entity with unidirectional #ManyToOne relation in SpringBoot application

I have two entities as below and the main problem is when I want to update the AccountRequestStatus entity. I save the integer enum code of AccountRequestStatusEnum in the database to persist the AcountRequest status in the whole application.
AccountRequestStatusEnum
public enum AccountRequestStatusEnum {
INITIAL(0),
SUCCESS(1);
private final Integer type;
AccountRequestStatusEnum(Integer type) {
this.type = type;
}
public Integer getType() {
return type;
}
public static AccountRequestStatusEnum of(Integer type) {
for (AccountRequestStatusEnum accountRequestStatusEnum : AccountRequestStatusEnum.values()) {
if (type.equals(accountRequestStatusEnum.getType()))
return accountRequestStatusEnum;
}
return null;
}
}
AccountRequest
#Entity
#Table(name = "T_ACCOUNT_REQUEST", uniqueConstraints = {#UniqueConstraint(columnNames = {"ACCOUNT_NO", "MESSAGE_ID"})})
#SequenceGenerator(
name = "SEQ_T_ACCOUNT_REQUEST",
sequenceName = "SEQ_T_ACCOUNT_REQUEST",
allocationSize = 1)
#AllArgsConstructor
#NoArgsConstructor
#EqualsAndHashCode(callSuper = false)
#ToString
public class AccountRequest extends AbstractAuditingEntity {
private Long id;
private String messageId;
private String issuer;
private EventType type;
private EventName name;
private String accountNo;
private LocalDateTime dateTime;
#Id
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "SEQ_T_ACCOUNT_REQUEST")
#Column(name = "ID", nullable = true, insertable = true, updatable = true, precision = 0)
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
#Column(name = "MESSAGE_ID")
public String getMessageId() {
return messageId;
}
public void setMessageId(String messageId) {
this.messageId = messageId;
}
#Column(name = "ISSUER")
public String getIssuer() {
return issuer;
}
public void setIssuer(String issuer) {
this.issuer = issuer;
}
#Transient
public EventType getType() {
return type;
}
public void setType(EventType type) {
this.type = type;
}
#Column(name = "TYPE")
public Integer getEventTypeCode() {
if (Objects.nonNull(type)) {
return type.getType();
} else return null;
}
public void setEventTypeCode(Integer typeCode) {
type = EventType.of(typeCode);
}
#Transient
public EventName getName() {
return name;
}
public void setName(EventName name) {
this.name = name;
}
#Column(name = "NAME")
public Integer getEventNameCode() {
if (Objects.nonNull(name)) {
return name.getType();
} else return null;
}
public void setEventNameCode(Integer type) {
name = EventName.of(type);
}
#Column(name = "ACCOUNT_NO")
public String getAccountNo() {
return accountNo;
}
public void setAccountNo(String accountNo) {
this.accountNo = accountNo;
}
#Column(name = "DATE_TIME")
public LocalDateTime getDateTime() {
return dateTime;
}
public void setDateTime(LocalDateTime dateTime) {
this.dateTime = dateTime;
}
}
AccountRequestStatus
#Entity
#Table(name = "T_ACCOUNT_REQUEST_STATUS")
#SequenceGenerator(
name = "SEQ_T_ACCOUNT_REQUEST_STATUS",
sequenceName = "SEQ_T_ACCOUNT_REQUEST_STATUS",
allocationSize = 1
)
#AllArgsConstructor
#NoArgsConstructor
#ToString
public class AccountRequestStatus extends AbstractAuditingEntity {
private Long id;
private AccountRequestStatusEnum accountRequestStatusEnum;
private AccountRequest accountRequest;
#Id
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "SEQ_T_ACCOUNT_REQUEST_STATUS")
#Column(name = "ID", nullable = true, insertable = true, updatable = true, precision = 0)
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
#Transient
public AccountRequestStatusEnum getAccountRequestStatusEnum() {
return accountRequestStatusEnum;
}
public void setAccountRequestStatusEnum(AccountRequestStatusEnum accountRequestStatusEnum) {
this.accountRequestStatusEnum = accountRequestStatusEnum;
}
#Column(name = "ACCOUNT_REQUEST_STATUS")
public Integer getAccountRequestStatusCode() {
if (Objects.nonNull(accountRequestStatusEnum)) {
return accountRequestStatusEnum.getType();
} else return null;
}
public void setAccountRequestStatusCode(Integer type) {
accountRequestStatusEnum = AccountRequestStatusEnum.of(type);
}
#ManyToOne(targetEntity = AccountRequest.class)
#JoinColumn(name = "ACCOUNT_REQUEST", referencedColumnName = "ID")
public AccountRequest getAccountRequest() {
return accountRequest;
}
public void setAccountRequest(AccountRequest accountRequest) {
this.accountRequest = accountRequest;
}
}
The first time that an account request comes from MQ my to application, I save the initial code of AccountRequestStatusEnum in service like below. This status persists properly and there is no problem, but when I want to update the AccountRequestStatus and add a new success code of AccountRequestStatusEnum (in another service) it won't be saved in DB.
This is the first service that is called after receiving the account request and saving the initial code.
#Service
#Transactional(readOnly = true)
public class AccountRequestServiceImpl implements IAccountRequestService {
#Value("${mq.event_argument_key}")
private String eventArgumentKey;
private final AccountRequestRepository accountRequestRepository;
private final AccountRequestStatusServiceImpl mqRequestStatusService;
private final EventToAccountRequestEntityMapper eventMapper;
private final AccountRequestMapper accountRequestMapper;
#Autowired
public AccountRequestServiceImpl(AccountRequestRepository accountRequestRepository,
AccountRequestStatusServiceImpl mqRequestStatusService,
EventToAccountRequestEntityMapper eventMapper,
AccountRequestMapper accountRequestMapper) {
this.accountRequestRepository = accountRequestRepository;
this.mqRequestStatusService = mqRequestStatusService;
this.eventMapper = eventMapper;
this.accountRequestMapper = accountRequestMapper;
}
#Override
#Transactional(propagation = Propagation.REQUIRES_NEW)// to prevent rollback for whole receive method in mq service
public void saveAccountRequest(Event event) {
AccountRequest accountRequest = eventMapper.eventToAccountRequest(event, eventArgumentKey);
accountRequestRepository.save(accountRequest);
AccountRequestDto accountRequestDto = accountRequestMapper.toDto(accountRequest);
saveAccountRequestStatus(accountRequestDto, AccountRequestStatusEnum.INITIAL);
}
private void saveAccountRequestStatus(AccountRequestDto accountRequestDto, AccountRequestStatusEnum status) {
AccountRequestStatusDto accountRequestStatusDto = new AccountRequestStatusDto();
accountRequestStatusDto.setAccountRequestStatusEnum(status);
accountRequestStatusDto.setAccountRequestDto(accountRequestDto);
mqRequestStatusService.saveAccountRequestStatus(accountRequestStatusDto);
}
}
This is the second service that should save the success code of AccountRequestStatus.
#Service
#Transactional(readOnly = true)
public class SyncLegacyAccountServiceImpl implements ISyncLegacyAccountService {
#Value("${mq.event_argument_key}")
private String eventArgumentKey;
#Value("${range.account_title_code}")
private String accountTitleCode;
private static final Logger log = LoggerFactory.getLogger(SyncLegacyAccountServiceImpl.class);
private final AccountMapRepository accountMapRepository;
private final AccountRequestRepository accountRequestRepository;
private final CustomerRepository customerRepository;
private final CustomerPersonRepository customerPersonRepository;
private final CustomerCompanyRepository customerCompanyRepository;
private final IMQService iMQService;
private final AccountRequestStatusServiceImpl accountRequestStatusServiceImpl;
private final GalaxyApi galaxyApi;
private final RangeApi rangeApi;
private final CustomerMapper customerMapper;
private final InquiryMapper inquiryMapper;
private final AccountRequestMapper accountRequestMapper;
private final EventToAccountRequestEntityMapper eventToAccountRequestMapper;
#Override
public void handleSyncRequest(Event event) {
saveSuccessfulAccountStatus(event); // ****** This is the main issue******
try {
CustomerAccountResponseDto galaxyData = getGalaxyData(event);
Optional<AccountMapEntity> optAccountMapEntity = accountMapRepository.findByNewAccountNo(event.getArgument().get(eventArgumentKey).toString());
if (!optAccountMapEntity.isPresent()) {
//openAccount(event);
} else {
AccountMapEntity accountMapEntity = optAccountMapEntity.get();
CustomerAccountResponseDto customerData = getCustomerData(accountMapEntity);
// save in legacy
}
} catch (Exception exception) {
handleEventRequestException(exception, event);
}
}
private void handleEventRequestException(Exception exception, Event event) {
if (exception instanceof RangeServiceException) {
log.error("Something went wrong with the Range service!");
throw new RangeServiceException();
} else if (exception instanceof GalaxySystemException) {
log.error("Something went wrong with the Galaxy service!");
NotifyAccountChangeResponse notifyAccountChangeResponse = MQUtil.buildAccountChangeResponse(new GalaxySystemException(), null, event.getMessageId());
iMQService.send(notifyAccountChangeResponse);
throw new GalaxySystemException();
}
}
public void saveSuccessfulAccountStatus(Event event) {
AccountRequest accountRequest = eventToAccountRequestMapper.eventToAccountRequest(event, eventArgumentKey);
AccountRequestDto accountRequestDto = accountRequestMapper.toDto(accountRequest);
saveAccountRequestStatus(accountRequestDto, AccountRequestStatusEnum.SUCCESS);
}
public void saveAccountRequestStatus(AccountRequestDto accountRequestDto, AccountRequestStatusEnum status) {
AccountRequestStatusDto accountRequestStatusDto = new AccountRequestStatusDto();
accountRequestStatusDto.setAccountRequestStatusEnum(status);
accountRequestStatusDto.setAccountRequestDto(accountRequestDto);
accountRequestStatusServiceImpl.saveAccountRequestStatus(accountRequestStatusDto);
}
}
UPDATE
AccountRequestStatusServiceImpl
#Service
#Transactional(readOnly = true)
public class AccountRequestStatusServiceImpl implements IAccountRequestStatusService {
private final AccountRequestStatusRepository accountRequestStatusRepository;
private final AccountRequestStatusMapper accountRequestStatusMapper;
#Autowired
public AccountRequestStatusServiceImpl(AccountRequestStatusRepository accountRequestStatusRepository,
AccountRequestStatusMapper accountRequestStatusMapper) {
this.accountRequestStatusRepository = accountRequestStatusRepository;
this.accountRequestStatusMapper = accountRequestStatusMapper;
}
#Override
#Transactional
public void saveAccountRequestStatus(AccountRequestStatusDto accountRequestStatusDto) {
AccountRequestStatus accountRequestStatus = accountRequestStatusMapper.toAccountRequestStatus(accountRequestStatusDto);
accountRequestStatusRepository.save(accountRequestStatus);
}
}
AccountRequestDto
#Data
public class AccountRequestDto {
private Long id;
private String messageId;
private String issuer;
private EventType type;
private EventName name;
private String accountNo;
private LocalDateTime dateTime;
}
AccountRequestStatusDto
#Data
public class AccountRequestStatusDto {
private Long id;
private AccountRequestStatusEnum accountRequestStatusEnum;
private AccountRequestDto accountRequestDto;
}
AccountRequestStatusMapper
#Mapper(componentModel = "spring")
public interface AccountRequestStatusMapper extends EntityToDtoMapper<AccountRequestStatusDto, AccountRequestStatus>, DtoToEntityMapper<AccountRequestStatusDto, AccountRequestStatus> {
#Mapping(target = "accountRequest.id", source = "accountRequestDto.id")
#Mapping(target = "accountRequest.messageId", source = "accountRequestDto.messageId")
#Mapping(target = "accountRequest.issuer", source = "accountRequestDto.issuer")
#Mapping(target = "accountRequest.type", source = "accountRequestDto.type")
#Mapping(target = "accountRequest.name", source = "accountRequestDto.name")
#Mapping(target = "accountRequest.accountNo", source = "accountRequestDto.accountNo")
#Mapping(target = "accountRequest.dateTime", source = "accountRequestDto.dateTime")
#Named(value = "toAccountRequestStatus")
AccountRequestStatus toAccountRequestStatus(AccountRequestStatusDto accountRequestStatusDto);
#Mapping(target = "accountRequestDto.id", source = "accountRequest.id")
#Mapping(target = "accountRequestDto.messageId", source = "accountRequest.messageId")
#Mapping(target = "accountRequestDto.issuer", source = "accountRequest.issuer")
#Mapping(target = "accountRequestDto.type", source = "accountRequest.type")
#Mapping(target = "accountRequestDto.name", source = "accountRequest.name")
#Mapping(target = "accountRequestDto.accountNo", source = "accountRequest.accountNo")
#Mapping(target = "accountRequestDto.dateTime", source = "accountRequest.dateTime")
#Named(value = "toAccountRequestStatusDto")
AccountRequestStatusDto toAccountRequestStatusDto(AccountRequestStatus accountRequestStatus);
}
You are not setting the id field of accountRequestStatus before saving. How is Hibernate supposed to know which entity to update? I'm guessing it is just generating a new id since you don't provide one. You need to somehow get the id of the already created accountRequestStatus and set it before updating. Or alternatively get the persisted accountRequestStatus entity and modify it.
That being said, I think you may be overcomplicating things here. Why does AccountRequestStatus need to be an entity? It does not seem to have an identity itself, it seems more like an attribute of AccountRequest. Can't you just add an AccountRequestStatusEnum field in AccountRequest, being after all a one-to-one relationship? If you are enclosing the status in a class because you foresee it may have more fields in the future, consider making it #Embeddable instead of an #Entity.
The general rule to know if a class must be an #Entity is that it must have an identity. The status of a request does not look like a good candidate, since its identity can simply be that of the request. Having the status be an #Entity means you now have an artificial id, which, surprise, is the root cause of your problem right now. If you want to better understand this topic, I recommend reading into the difference between #Entity and #Embeddable, and if you are feeling theoretical, read a bit about DDD(domain driven design).

JPA Optimistic Lock Exception in single thread

I'm trying to update a basic deletion timestamp using a JPA persistence class. Here's the code:
public void delete(Document document, EntityManager em, SessionContext ctx)
throws MyException {
try {
ctx.getUserTransaction().begin();
DocumentDB documentDB = em.find(WDSDocument.class, document.getId());
if (documentDB != null) {
em.lock(documentDB, LockModeType.WRITE);
documentDB.setDeletedAt(new Timestamp(System.currentTimeMillis()));
em.merge(documentDB);
em.flush(); // OptimisticLockException raised here
}
ctx.getUserTransaction().commit();
if (log.isDebugEnabled()) log.debug("DELETED " + document.getId());
} catch (Exception e) {
ctx.getUserTransaction().rollback();
throw new MyException(Utils.getError("ERR9", new String[] { "" + document.getId(), e.getMessage() }),
e);
}
}
The document with the same Id as document is already stored in a DB and I would like to update a single field.
The em.flush() line is raising the exception, as if another user (thread) was trying to update the same object, but this isn't the case in my application.
I've read about OptimisticLockException in JPA and I understand that it can be raised when the same user tries to update an object twice in a row, without flushing/committing to a DB first.
Apparently this error happens whenever I try to update the documentDB deletion timestamp for any object, so I guess there should be something inherently wrong with my delete() method that double updates the object itself. I was not able to troubleshoot it correctly so far.
EDITED
Here's my DocumentDB class
#Entity
#Table(name = "DOCUMENTS", schema = "WP")
public class DocumentDB extends AbstractEntity {
private static final long serialVersionUID = -98765134L;
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "ID_DOCUMENT", nullable = false)
private int id;
#Column(name = "SOURCE")
private String source = null;
#Column(name = "CREATION_DATE")
private Date creationDate;
#Column(name = "TITLE")
private String title = null;
#Column(name = "AUTHOR")
private String author = null;
#Column(name = "URL")
private String url = null;
#Column(name = "DELETED_AT")
private Timestamp deletedAt = null;
#Override
public Integer getId() {
return id;
}
#Override
public void setId(int id) {
this.id = id;
}
public Date getCreationDate() {
return creationDate;
}
public void setCreationDate(Date creationDate) {
this.creationDate = creationDate;
}
public String getSource() {
return source;
}
public void setSource(String source) {
this.source = source;
}
public String getUrl() {
return url;
}
public void setUrl(String url) {
this.url = url;
}
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
public String getAuthor() {
return author;
}
public void setAuthor(String author) {
this.author = author;
}
public Timestamp getDeletedAt() {
return deletedAt;
}
public void setDeletedAt(Timestamp deletedAt) {
this.deletedAt = deletedAt;
}
}
While its abstract superclass is:
#MappedSuperclass
public abstract class AbstractEntity implements Serializable {
private static final long serialVersionUID = -98765134L;
public abstract Integer getId();
public abstract void setId(int id);
/**
* Creator of the record
*/
#Column(name = "USER_CREATION", nullable = false)
protected String userCreation;
/**
* Timestamp of creation of the record
*/
#Column(name = "DATE_CREATION", nullable = false)
protected Timestamp dateCreation;
/**
* User of the last change of the record
*/
#Column(name = "USER_LAST_CHANGE", nullable = false)
protected String userLastChange;
/**
* Timestamp of the last change of the record
*/
#Column(name = "DATE_LAST_CHANGE", nullable = false)
protected Timestamp dateLastChange;
/**
* Progressive of the variation of the record:
* used in optimistic locking of entity manager
* to avoid conflicts in insert/update
*/
#Version
#Column(name = "PG_VER_REC", nullable = false)
protected int progressiveVariationRecord;
public String getUserCreation() {
return userCreation;
}
public void setUserCreation(String userCreation) {
this.userCreation = userCreation;
}
public Timestamp getDateCreation() {
return dateCreation;
}
public void setDateCreation(Timestamp dateCreation) {
this.dateCreation = dateCreation;
}
public String getUserLastChange() {
return userLastChange;
}
public void setUserLastChange(String userLastChange) {
this.userLastChange = userLastChange;
}
public Timestamp getDateLastChange() {
return dateLastChange;
}
public void setDateLastChange(Timestamp dateLastChange) {
this.dateLastChange = dateLastChange;
}
public int getProgressiveVariationRecord() {
return progressiveVariationRecord;
}
public void setProgressiveVariationRecord(int progressiveVariationRecord) {
this.progressiveVariationRecord = progressiveVariationRecord;
}
}
Would you please provide some guidance on the next steps to perform in order to understand better this issue?
UPDATE
I haven't found the root cause of the issue so far. I suspect that my implementation of EclipseLink JPA does weird things when updating entities inside a for loop.
Unfortunately at the moment I don't have the time and the resources to dig deeper and I am using Pessimistic Locking as a workaround. Hope to find the real problem sometime in the future.

Hibernate Annotations Perform Inner Join

Hello I have just started learning hibernate. Please correct me where I am doing mistake. I want do a one-to-many relationship between two tables using a join table using hibernate annotations.
create table assembly
(
assembly_id serial primary key,
number text,
user_id int
);
create table assembly_properties
(
property_id serial primary key,
property_name text,
property_type text
);
create table assembly_properties_mapping
(
mapping_id serial primary key,
assembly_id int,
property_id int,
property_value text,
CONSTRAINT FK_assembly_id FOREIGN KEY (assembly_id) REFERENCES assembly(assembly_id),
CONSTRAINT FK_property_id FOREIGN KEY (property_id) REFERENCES assembly_properties(property_id)
);
I have created these three table in postgres sql database. Below is my Assembly.class
package com.development.wrapper;
#Entity
#Table(name = "assembly")
public class Assembly {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "assembly_id")
private int assembly_id;
#Column(name = "number")
private String number;
#Column(name ="UserID")
private int userId;
#Column
#ElementCollection(targetClass = AssemblyProperties.class)
private Set<AssemblyProperties> assembly_properties;
public int getAssembly_id() {
return assembly_id;
}
public void setAssembly_id(int assembly_id) {
this.assembly_id = assembly_id;
}
public String getNumber() {
return number;
}
public void setNumber(String number) {
this.number = number;
}
public int getUserId() {
return userId;
}
public void setUserId(int userId) {
this.userId = userId;
}
#OneToMany(targetEntity = AssemblyProperties.class, cascade = CascadeType.ALL)
#JoinTable(name = "assembly_properties_mapping", joinColumns = { #JoinColumn(name = "assembly_id") }, inverseJoinColumns = { #JoinColumn(name = "property_id") })
public Set<AssemblyProperties> getAssembly_properties() {
return assembly_properties;
}
public void setAssembly_properties(Set<AssemblyProperties> assembly_properties) {
this.assembly_properties = assembly_properties;
}
}
Below is AssemblyProperties.class
package com.development.wrapper;
#Entity
#Table(name = "assembly_properties")
public class AssemblyProperties {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "property_id")
private int property_id;
#Column(name = "property_name")
private String property_name;
#Column(name = "property_type")
private String property_type;
public int getProperty_id() {
return property_id;
}
public void setProperty_id(int property_id) {
this.property_id = property_id;
}
public String getProperty_name() {
return property_name;
}
public void setProperty_name(String property_name) {
this.property_name = property_name;
}
public String getProperty_type() {
return property_type;
}
public void setProperty_type(String property_type) {
this.property_type = property_type;
}
}
When I am trying to load data in database table as given below I am getting an error Failed to create sessionFactory object.org.hibernate.MappingException: Could not determine type for: com.development.wrapper.AssemblyProperties, at table: Assembly_assembly_properties, for columns: [org.hibernate.mapping.Column(assembly_properties)]
Exception in thread "main" java.lang.ExceptionInInitializerError
below is code I am trying to run
public class Test
{
SessionFactory factory;
public Test() throws Exception
{
try
{
factory = new AnnotationConfiguration().configure().
addPackage("com.development.wrapper"). //add package if used.
addAnnotatedClass(Assembly.class).buildSessionFactory();
}
catch (Throwable ex)
{
System.err.println("Failed to create sessionFactory object." + ex);
throw new ExceptionInInitializerError(ex);
}
}
public Integer addClass(Assembly assembly)
{
Session session = factory.openSession();
Transaction tx = null;
Integer assemblyid = null;
try
{
tx = session.beginTransaction();
assemblyid = (Integer) session.save(assembly);
System.out.println(assemblyid);
tx.commit();
}
catch (HibernateException e)
{
if (tx != null)
tx.rollback();
e.printStackTrace();
}
finally
{
session.close();
}
return assemblyid;
}
public static void main(String[] args) throws Exception {
Set<AssemblyProperties> assemblyProperties = new HashSet<AssemblyProperties>();
AssemblyProperties ass=new AssemblyProperties();
ass.setProperty_name("xx");
ass.setProperty_type("List");
assemblyProperties.add(ass);
Assembly assembly =new Assembly();
assembly.setAssembly_properties(assemblyProperties);
assembly.setNumber("aaa");
assembly.setUserId(1);
Test test=new Test();
test.addClass(assembly);
}
}
Please help me to resolve this error/ Thanks in advance.
Hibernate can't handle when the annotations for public setters and private fields are mixed in one class.
A possible solution would be to make all of your annotations at the public setters instead of mixing it between the private fields and the public setters, that way you can avoid the case where there are annotations both at public and private access modifiers.
Your annotations are conflicting. this:
#Column
#ElementCollection(targetClass = AssemblyProperties.class)
private Set<AssemblyProperties> assembly_properties;
and this:
#OneToMany(targetEntity = AssemblyProperties.class, cascade = CascadeType.ALL)
#JoinTable(name = "assembly_properties_mapping", joinColumns = { #JoinColumn(name = "assembly_id") }, inverseJoinColumns = { #JoinColumn(name = "property_id") })
public Set<AssemblyProperties> getAssembly_properties() {
return assembly_properties;
}
just remove first annotaions over private field (assembly_properties).

hibernate how to extend one entity to all the entities

I have two fields that should appear in each table. So I wanted to create an entity that will hold these fields and the rest of my entities inherited these fields
But when I run the query I get the error - org.hibernate.QueryException: could not resolve property: active of: com.db.tables.PersonTable
what i'm doing wrong?
Base class all entities should inherit these fields
#XmlRootElement
#Inheritance(strategy=InheritanceType.SINGLE_TABLE)
public class BaseTable implements Serializable
{
private static final long serialVersionUID = 1L;
#Column(name = "Updated")
#JsonProperty
#NotNull
protected Timestamp updated;
#Column(name = "Active")
#JsonProperty
#NotNull
protected byte active;
public BaseTable ()
{
active = (byte)1;
updated = DbUtils.getCurrentTimeStamp();
}
public byte getActive()
{
return active;
}
public void setActive(byte active)
{
this.active = active;
}
public Timestamp getUpdated()
{
return updated;
}
public void setUpdated(Timestamp updated)
{
this.updated = updated;
}
#Override
public String toString()
{
return new ReflectionToStringBuilder(this, ToStringStyle.SHORT_PREFIX_STYLE).toString();
}
#Override
public int hashCode()
{
final int prime = 31;
int result = 1;
result = prime * result + active;
result = prime * result + ((updated == null) ? 0 : updated.hashCode());
return result;
}
#Override
public boolean equals(Object obj)
{
if (this == obj) return true;
if (obj == null) return false;
if (getClass() != obj.getClass()) return false;
BaseTable other = (BaseTable) obj;
if (active != other.active) return false;
if (updated == null)
{
if (other.updated != null) return false;
}
else if (!updated.equals(other.updated)) return false;
return true;
}
}
A class that inherits
#Entity(name = "Persons")
#Table(name = "Persons")
public class PersonTable extends BaseTable implements Serializable
{
private static final long serialVersionUID = -5793514680136648542L;
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "PersonId")
private short personId;
#OneToOne(cascade = CascadeType.ALL)
#JoinColumn(name="personId")
PortalUserTable portalUser;
//getters&settersand more fields
}
one more class that inherits
#Entity(name = "PortalUser")
#Table(name = "PortalUser")
public class PortalUserTable extends BaseTable implements Serializable
{
private static final long serialVersionUID = -5793514680136648542L;
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "PersonId")
private short personId;
#OneToOne
(mappedBy = "portalUser")
PersonTable person;
//getters&settersand more fields
}
the query
public ObjectDaoResponse getAllTigrisUsers() throws JsonProcessingException
{
try
{
Query q = sessionFactory.getCurrentSession().createQuery("SELECT new com.db.queries.users.User( u.portalUserId ,p.personName) FROM PortalUsers u INNER JOIN u.person p WHERE portalUserId = p.personId AND p.active = 1 AND u.active = 1");
List<TigrisUser> l = q.list();
return ObjectDaoResponse.getAnOkResponse(l);
}
catch(Exception e)
{
System.out.println(e);
return ObjectDaoResponse.getGeneralFailureResponse();
}
}
#MappedSuperclass
public class BaseTable ...
I would suggest to change naming convention Base(Table) -> Base(Entity). Do the same for all entity classes.
You should take care of inheritance strategy - https://en.wikibooks.org/wiki/Java_Persistence/Inheritance

Vaadin JPAContainer: ManytoOne relation with EmbeddedID

There are questions similar but not quite. In those cases (and in the JPAContainer examples) the Entity part of the ManyToOne relationship has a single key. In my case it is an embedded id.
My code is based on the Address Book example.
Here are the three entities:
#Entity
#Table(name = "tutorial")
#XmlRootElement
#NamedQueries({
#NamedQuery(name = "Tutorial.findAll", query = "SELECT t FROM Tutorial t"),
#NamedQuery(name = "Tutorial.findById", query = "SELECT t FROM Tutorial t WHERE t.tutorialPK.id = :id"),
#NamedQuery(name = "Tutorial.findByTutorialTypeId", query = "SELECT t FROM Tutorial t WHERE t.tutorialPK.tutorialTypeId = :tutorialTypeId"),
#NamedQuery(name = "Tutorial.findByMessage", query = "SELECT t FROM Tutorial t WHERE t.message = :message")})
public class Tutorial implements Serializable {
private static final long serialVersionUID = 1L;
#EmbeddedId
protected TutorialPK tutorialPK;
#Basic(optional = false)
#NotNull
#Size(min = 1, max = 245)
#Column(name = "message")
private String message;
#JoinColumn(name = "tutorial_type_id", referencedColumnName = "id", insertable = false, updatable = false)
#ManyToOne(optional = false)
private TutorialType tutorialType;
public Tutorial() {
}
public Tutorial(TutorialPK tutorialPK) {
this.tutorialPK = tutorialPK;
}
public Tutorial(TutorialPK tutorialPK, String message) {
this.tutorialPK = tutorialPK;
this.message = message;
}
public Tutorial(int id, int tutorialTypeId) {
this.tutorialPK = new TutorialPK(id, tutorialTypeId);
}
public TutorialPK getTutorialPK() {
return tutorialPK;
}
public void setTutorialPK(TutorialPK tutorialPK) {
this.tutorialPK = tutorialPK;
}
public String getMessage() {
return message;
}
public void setMessage(String message) {
this.message = message;
}
public TutorialType getTutorialType() {
return tutorialType;
}
public void setTutorialType(TutorialType tutorialType) {
this.tutorialType = tutorialType;
}
#Override
public int hashCode() {
int hash = 0;
hash += (tutorialPK != null ? tutorialPK.hashCode() : 0);
return hash;
}
#Override
public boolean equals(Object object) {
// TODO: Warning - this method won't work in the case the id fields are not set
if (!(object instanceof Tutorial)) {
return false;
}
Tutorial other = (Tutorial) object;
if ((this.tutorialPK == null && other.tutorialPK != null) || (this.tutorialPK != null && !this.tutorialPK.equals(other.tutorialPK))) {
return false;
}
return true;
}
#Override
public String toString() {
return "games.jwrestling.server.game.db.persistence.Tutorial[ tutorialPK=" + tutorialPK + " ]";
}
}
And the EmbeddedId class:
#Embeddable
public class TutorialPK implements Serializable {
#Basic(optional = false)
#NotNull
#Column(name = "id")
private int id;
#Basic(optional = false)
#NotNull
#Column(name = "tutorial_type_id")
private int tutorialTypeId;
public TutorialPK() {
}
public TutorialPK(int id, int tutorialTypeId) {
this.id = id;
this.tutorialTypeId = tutorialTypeId;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public int getTutorialTypeId() {
return tutorialTypeId;
}
public void setTutorialTypeId(int tutorialTypeId) {
this.tutorialTypeId = tutorialTypeId;
}
#Override
public int hashCode() {
int hash = 0;
hash += (int) id;
hash += (int) tutorialTypeId;
return hash;
}
#Override
public boolean equals(Object object) {
// TODO: Warning - this method won't work in the case the id fields are not set
if (!(object instanceof TutorialPK)) {
return false;
}
TutorialPK other = (TutorialPK) object;
if (this.id != other.id) {
return false;
}
if (this.tutorialTypeId != other.tutorialTypeId) {
return false;
}
return true;
}
#Override
public String toString() {
return "games.jwrestling.server.game.db.persistence.TutorialPK[ id=" + id + ", tutorialTypeId=" + tutorialTypeId + " ]";
}
}
And another one:
#Entity
#Table(name = "tutorial_type")
#XmlRootElement
#NamedQueries({
#NamedQuery(name = "TutorialType.findAll", query = "SELECT t FROM TutorialType t"),
#NamedQuery(name = "TutorialType.findById", query = "SELECT t FROM TutorialType t WHERE t.id = :id"),
#NamedQuery(name = "TutorialType.findByType", query = "SELECT t FROM TutorialType t WHERE t.type = :type"),
#NamedQuery(name = "TutorialType.findByDescription", query = "SELECT t FROM TutorialType t WHERE t.description = :description")})
public class TutorialType implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#Basic(optional = false)
#NotNull
#GeneratedValue(strategy = GenerationType.TABLE, generator = "TutorialTypeGen")
#TableGenerator(name = "TutorialTypeGen", table = "jwrestling_id",
pkColumnName = "tablename",
valueColumnName = "last_id",
pkColumnValue = "tutorial_type",
allocationSize = 1,
initialValue = 1)
#Column(name = "id")
private Integer id;
#Basic(optional = false)
#NotNull
#Size(min = 1, max = 45)
#Column(name = "type")
private String type;
#Size(max = 245)
#Column(name = "description")
private String description;
#OneToMany(cascade = CascadeType.ALL, mappedBy = "tutorialType")
private List<Tutorial> tutorialList;
public TutorialType() {
}
public TutorialType(Integer id) {
this.id = id;
}
public TutorialType(Integer id, String type) {
this.id = id;
this.type = type;
}
public TutorialType(String type, String desc) {
this.type = type;
this.description = desc;
}
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getType() {
return type;
}
public void setType(String type) {
this.type = type;
}
public String getDescription() {
return description;
}
public void setDescription(String description) {
this.description = description;
}
#XmlTransient
public List<Tutorial> getTutorialList() {
return tutorialList;
}
public void setTutorialList(List<Tutorial> tutorialList) {
this.tutorialList = tutorialList;
}
#Override
public int hashCode() {
int hash = 0;
hash += (id != null ? id.hashCode() : 0);
return hash;
}
#Override
public boolean equals(Object object) {
// TODO: Warning - this method won't work in the case the id fields are not set
if (!(object instanceof TutorialType)) {
return false;
}
TutorialType other = (TutorialType) object;
if ((this.id == null && other.id != null) || (this.id != null && !this.id.equals(other.id))) {
return false;
}
return true;
}
#Override
public String toString() {
return "games.jwrestling.server.game.db.persistence.TutorialType[ id=" + id + " ]";
}
}
I got the form set up and it works fine when I edit the items, but creating I get errors because the TutorialPK is null:
Caused by: Exception [EclipseLink-4002] (Eclipse Persistence Services - 2.6.2.v20151217-774c696): org.eclipse.persistence.exceptions.DatabaseException
Internal Exception: com.mysql.jdbc.exceptions.jdbc4.MySQLIntegrityConstraintViolationException: Column 'tutorial_type_id' cannot be null
Here are the related Vaadin code:
public final class TutorialEditor extends Window implements Button.ClickListener,
FormFieldFactory {
private final Item tutorialItem;
private final Form editorForm;
private final Button saveButton;
private final Button cancelButton;
public TutorialEditor(Item tutorialItem) {
this.tutorialItem = tutorialItem;
editorForm = new Form();
editorForm.setFormFieldFactory(this);
editorForm.setBuffered(true);
editorForm.setImmediate(true);
editorForm.setItemDataSource(tutorialItem, Arrays.asList("message",
"tutorialType"));
saveButton = new Button("Save", this);
cancelButton = new Button("Cancel", this);
editorForm.getFooter().addComponent(saveButton);
editorForm.getFooter().addComponent(cancelButton);
setSizeUndefined();
setContent(editorForm);
setCaption("New Tutorial");
}
#Override
public void buttonClick(Button.ClickEvent event) {
if (event.getButton() == saveButton) {
editorForm.commit();
fireEvent(new EditorSavedEvent(this, tutorialItem));
} else if (event.getButton() == cancelButton) {
editorForm.discard();
}
close();
}
#Override
public Field<?> createField(Item item, Object propertyId, Component uiContext) {
Field field = DefaultFieldFactory.get().createField(item, propertyId,
uiContext);
if ("tutorialType".equals(propertyId)) {
field = new TutorialTypeSelector();
} else if (field instanceof TextField) {
((TextField) field).setNullRepresentation("");
}
field.addValidator(new BeanValidator(Tutorial.class, propertyId
.toString()));
return field;
}
public void addListener(EditorSavedListener listener) {
try {
Method method = EditorSavedListener.class.getDeclaredMethod(
"editorSaved", new Class[]{EditorSavedEvent.class});
addListener(EditorSavedEvent.class, listener, method);
} catch (final java.lang.NoSuchMethodException e) {
// This should never happen
throw new java.lang.RuntimeException(
"Internal error, editor saved method not found");
}
}
public void removeListener(EditorSavedListener listener) {
removeListener(EditorSavedEvent.class, listener);
}
public static class EditorSavedEvent extends Component.Event {
private final Item savedItem;
public EditorSavedEvent(Component source, Item savedItem) {
super(source);
this.savedItem = savedItem;
}
public Item getSavedItem() {
return savedItem;
}
}
public interface EditorSavedListener extends Serializable {
public void editorSaved(EditorSavedEvent event);
}
And another one:
class TutorialTypeSelector extends CustomField<TutorialType> {
private final JPAContainer<TutorialType> container;
private final ComboBox type = new ComboBox();
public TutorialTypeSelector() {
container = JPAContainerFactory.make(TutorialType.class,
"JWPUJNDI");
setCaption("Type");
type.setContainerDataSource(container);
type.setItemCaptionPropertyId("type");
type.addListener(new Property.ValueChangeListener() {
#Override
public void valueChange(
com.vaadin.data.Property.ValueChangeEvent event) {
/*
* Modify the actual value of the custom field.
*/
if (type.getValue() == null) {
setValue(null, false);
} else {
TutorialType entity = container
.getItem(type.getValue()).getEntity();
setValue(entity, false);
}
}
});
}
#Override
protected Component initContent() {
CssLayout cssLayout = new CssLayout();
cssLayout.addComponent(type);
return cssLayout;
}
#Override
public void setPropertyDataSource(Property newDataSource) {
super.setPropertyDataSource(newDataSource);
setTutorialType((TutorialType) newDataSource.getValue());
}
#Override
public void setValue(TutorialType newValue) throws ReadOnlyException,
Converter.ConversionException {
super.setValue(newValue);
setTutorialType(newValue);
}
private void setTutorialType(TutorialType type) {
this.type.setValue(type != null ? type.getId() : null);
}
#Override
public Class<? extends TutorialType> getType() {
return TutorialType.class;
}
}
Any idea on how to populate this field?
Update:
Error after using #MapsId
Exception [EclipseLink-46] (Eclipse Persistence Services - 2.6.2.v20151217-774c696): org.eclipse.persistence.exceptions.DescriptorException
Exception Description: There should be one non-read-only mapping defined for the primary key field [tutorial.tutorial_type_id].
Descriptor: RelationalDescriptor(games.jwrestling.server.game.db.persistence.Tutorial --> [DatabaseTable(tutorial)])
Two ways. If using JPA 1.0, you will need to pull the value from the referenced tutorialType and manually add it to the tutorial.tutorialPK.tutorialTypeId. You didn't include the tutorialType entity, but if it's ID value is generated, you may need to persist it and flush before the value is assigned.
If using JPA 2.0, you can specify the #MapsId annotation in your entity, allowing JPA to set the tuturial.tutorialPK.tutorialTypeId value from the tutorial.tutorialType reference for you:
public class Tutorial implements Serializable {
private static final long serialVersionUID = 1L;
#EmbeddedId
protected TutorialPK tutorialPK;
#Basic(optional = false)
#NotNull
#Size(min = 1, max = 245)
#Column(name = "message")
private String message;
#MapsId("tutorialTypeId")
#ManyToOne(optional = false)
private TutorialType tutorialType;
You will not be able to change the TutorialType associated to a tutorial once it is created though - the only option is to delete the existing one and create a new one with the new values.

Categories