document not saving in spring jpa document manager application - java

I am developing a document management application in spring using jpa and MySQL. The application is currently accepting a document and its meta data from a user web form createOrUpdateDocumentForm.jsp into the controller DocumentController.java. However, the data is not making its way into the MySQL database. Can someone show me how to alter my code so that the document and its metadata get stored in the underlying database?
The flow of data (including the pdf document) seems to go through the following objects:
createOrUpdateDocumentForm.jsp //omitted for brevity, since it is sending data to controller (see below)
Document.java
DocumentController.java
ClinicService.java
JpaDocumentRepository.java
The MySQL database
I will summarize relevant parts of each of these objects as follows:
The jsp triggers the following method in DocumentController.java:
#RequestMapping(value = "/patients/{patientId}/documents/new", headers = "content-type=multipart/*", method = RequestMethod.POST)
public String processCreationForm(#ModelAttribute("document") Document document, BindingResult result, SessionStatus status, #RequestParam("file") final MultipartFile file) {
document.setCreated();
byte[] contents;
Blob blob = null;
try {
contents = file.getBytes();
blob = new SerialBlob(contents);
} catch (IOException e) {e.printStackTrace();}
catch (SerialException e) {e.printStackTrace();}
catch (SQLException e) {e.printStackTrace();}
document.setContent(blob);
document.setContentType(file.getContentType());
document.setFileName(file.getOriginalFilename());
System.out.println("----------- document.getContentType() is: "+document.getContentType());
System.out.println("----------- document.getCreated() is: "+document.getCreated());
System.out.println("----------- document.getDescription() is: "+document.getDescription());
System.out.println("----------- document.getFileName() is: "+document.getFileName());
System.out.println("----------- document.getId() is: "+document.getId());
System.out.println("----------- document.getName() is: "+document.getName());
System.out.println("----------- document.getPatient() is: "+document.getPatient());
System.out.println("----------- document.getType() is: "+document.getType());
try {System.out.println("[[[[BLOB LENGTH IS: "+document.getContent().length()+"]]]]");}
catch (SQLException e) {e.printStackTrace();}
new DocumentValidator().validate(document, result);
if (result.hasErrors()) {
System.out.println("result.getFieldErrors() is: "+result.getFieldErrors());
return "documents/createOrUpdateDocumentForm";
}
else {
this.clinicService.saveDocument(document);
status.setComplete();
return "redirect:/patients?patientID={patientId}";
}
}
When I submit a document through the web form in the jsp to the controller, the System.out.println() commands in the controller code output the following, which indicate that the data is in fact getting sent to the server:
----------- document.getContentType() is: application/pdf
----------- document.getCreated() is: 2013-12-16
----------- document.getDescription() is: paper
----------- document.getFileName() is: apaper.pdf
----------- document.getId() is: null
----------- document.getName() is: apaper
----------- document.getPatient() is: [Patient#564434f7 id = 1, new = false, lastName = 'Frank', firstName = 'George', middleinitial = 'B', sex = 'Male', dateofbirth = 2000-11-28T16:00:00.000-08:00, race = 'caucasian']
----------- document.getType() is: ScannedPatientForms
[[[[BLOB LENGTH IS: 712238]]]] //This indicates the file content was converted to blob
The Document.java model is:
#Entity
#Table(name = "documents")
public class Document {
#Id
#GeneratedValue
#Column(name="id")
private Integer id;
#ManyToOne
#JoinColumn(name = "client_id")
private Patient patient;
#ManyToOne
#JoinColumn(name = "type_id")
private DocumentType type;
#Column(name="name")
private String name;
#Column(name="description")
private String description;
#Column(name="filename")
private String filename;
#Column(name="content")
#Lob
private Blob content;
#Column(name="content_type")
private String contentType;
#Column(name = "created")
private Date created;
public Integer getId(){return id;}
public void setId(Integer i){id=i;}
protected void setPatient(Patient patient) {this.patient = patient;}
public Patient getPatient(){return this.patient;}
public void setType(DocumentType type) {this.type = type;}
public DocumentType getType() {return this.type;}
public String getName(){return name;}
public void setName(String nm){name=nm;}
public String getDescription(){return description;}
public void setDescription(String desc){description=desc;}
public String getFileName(){return filename;}
public void setFileName(String fn){filename=fn;}
public Blob getContent(){return content;}
public void setContent(Blob ct){content=ct;}
public String getContentType(){return contentType;}
public void setContentType(String ctype){contentType=ctype;}
public void setCreated(){created=new java.sql.Date(System.currentTimeMillis());}
public Date getCreated() {return this.created;}
#Override
public String toString() {return this.getName();}
public boolean isNew() {return (this.id == null);}
}
The ClinicService.java code that is called from the DocumentController is:
private DocumentRepository documentRepository;
private PatientRepository patientRepository;
#Autowired
public ClinicServiceImpl(DocumentRepository documentRepository, PatientRepository patientRepository) {
this.documentRepository = documentRepository;
this.patientRepository = patientRepository;
}
#Override
#Transactional
public void saveDocument(Document doc) throws DataAccessException {documentRepository.save(doc);}
The relevant code in JpaDocumentRepository.java is:
#PersistenceContext
private EntityManager em;
#Override
public void save(Document document) {
if (document.getId() == null) {this.em.persist(document);}
else {this.em.merge(document);}
}
Finally, the relevant parts of the SQL code that creates the database include:
CREATE TABLE IF NOT EXISTS documenttypes (
id INT(4) UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
name VARCHAR(80),
INDEX(name)
);
CREATE TABLE IF NOT EXISTS patients (
id INT(4) UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
first_name VARCHAR(30),
middle_initial VARCHAR(5),
last_name VARCHAR(30),
sex VARCHAR(20),
date_of_birth DATE,
race VARCHAR(30),
INDEX(last_name)
);
CREATE TABLE IF NOT EXISTS documents (
id int(11) UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
client_id int(4) UNSIGNED NOT NULL,
type_id INT(4) UNSIGNED,
name varchar(200) NOT NULL,
description text NOT NULL,
filename varchar(200) NOT NULL,
content mediumblob NOT NULL,
content_type varchar(255) NOT NULL,
created timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (client_id) REFERENCES patients(id),
FOREIGN KEY (type_id) REFERENCES documenttypes(id)
);
What changes do I make to this code so that it saves the document in the documents table of the MySQL database using jpa?

#CodeMed, it took me a while, but I was able to reproduce the issue. It might be a configuration issue : #PersistenceContext might be scanned twice, it might be scanned by your root-context and your web-context. This cause the #PersistenceContext to be shared, therefore it is not saving your data (Spring doesn't allow that). I found it weird that no messages or logs where displayed . if you tried this snippet below on you Save(Document document) you will see the actual error :
Session session = this.em.unwrap(Session.class);
session.persist(document);
To solve the problem, you can do the following (avoid the #PersistenceContext to be scanned twice) :
1- Make sure that all your controller are in a separate package like com.mycompany.myapp.controller, and in your web-context use the component-scan as <context:component-scan annotation-config="true" base-package="com.mycompany.myapp.controller" />
2- Make sure that others component are in differents package other than the controller package , for example : com.mycompany.myapp.dao, com.mycompany.myapp.service ....
and then in your root-context use the component-scan as
<context:component-scan annotation-config="true" base-package="com.mycompany.myapp.service, com.mycompany.myapp.dao" />
Or show me yours spring xml configurations and your web.xml, I will point you to the right direction

Your JPA mappings seem good. Obviously, #Lob requires data type to be byte[] / Byte[] / or java.sql.Blob. Based on that, plus your symptoms and debugging printout it seems your code doing the correct data manipulation (JPA annotations good), but the combination of spring + MySQL isn't commiting. This suggests a minor problem with your spring transactional config OR with your MySQL data type.
1. Transactional Behaviour
The relevant code in JpaDocumentRepository.java is:
#PersistenceContext
private EntityManager em;
#Override
public void save(Document document) {
if (document.getId() == null) {this.em.persist(document);}
else {this.em.merge(document);}
}
You're not using EJBs (hence no 'automatic' container-managed transactions).
You're using JPA within Servlets/java classes (hence you require 'manual' transaction demarcation - outside servlet container; in your code or via Spring config).
You are injecting the entity manager via #PersistenceContext (i.e. container-managed entity manager backed by JTA, not a Entity Manager resource-local transaction, em.getTransaction())
You have marked your 'parent' method as #Transactional (i.e. spring proprietary transcations - annotation later standardised in Java EE 7).
The annotations and code should give transactional behaviour. Do you have a Spring correctly configured for JTA transactions? (Using JtaTransactionManager, not DataSourceTransactionManager which gives JDBC driver local transactions) Spring XML should contain something very similar to:
<!-- JTA requires a container-managed datasource -->
<jee:jndi-lookup id="jeedataSource" jndi-name="jdbc/mydbname"/>
<!-- enable the configuration of transactional behavior based on annotations -->
<tx:annotation-driven transaction-manager="txManager"/>
<!-- a PlatformTransactionManager is still required -->
<bean id="txManager" class="org.springframework.transaction.jta.JtaTransactionManager" >
<!-- (this dependency "jeedataSource" must be defined somewhere else) -->
<property name="dataSource" ref="jeedataSource"/>
</bean>
Be suspicious of additional parameters / settings.
This is the manually coded version of what Spring must do (for understanding only - don't code this). Uses UserTransaction (JTA), not em.getTransaction() of type EntityTransaction (JDBC local):
// inject a reference to the servlet container JTA tx
#Resource UserTransaction jtaTx;
// servlet container-managed EM
#PersistenceContext private EntityManager em;
public void save(Document document) {
try {
jtaTx.begin();
try {
if (document.getId() == null) {this.em.persist(document);}
else {this.em.merge(document);}
jtaTx.commit();
} catch (Exception e) {
jtaTx.rollback();
// do some error reporting / throw exception ...
}
} catch (Exception e) {
// system error - handle exceptions from UserTransaction methods
// ...
}
}
2. MySQL Data Type
As shown here (at bottom), MySql Blobs are a bit special compared to other databases. The various Blobs and their maximum storage capacities are:
TINYBLOB - 255 bytes
BLOB - 65535 bytes
MEDIUMBLOB - 16,777,215 bytes (2^24 - 1)
LONGBLOB - 4G bytes (2^32 – 1)
If (2) turns out to be your problem:
increase the MySQL type to MEDIUMBLOB or LONGBLOB
investigate why you didn't see an error message (v important). Was your logging properly configured? Did you check logs?

I'm not a Hibernate-with-annotations expert (I've been using it since 2004, but with XML config). Anyway, I'm thinking that you're mixing annotations incorrectly. You've indicated that you don't want the file field persisted with #Transient, but you've also said it's a #Lob, which implies you do want it persisted. Looks like #Lob is winning, and Hibernate is trying to resolve the field to a column by using the field name.
Take off the #Lob and I think you'll be set.

This is not a direct answer to your question (sorry but I'm not a fan of hibernate so can't really help you there) but you should consider using a NoSQL database such as MongoDB rather than MySQL for a job like this. I've tried both and the NoSQL databases are a much better fit to this sort of requirement.
You will find that in situations like this it performs much better than MySQL can do and SpringData MongoDB allows you to very easily save and load Java objects that automatically get mapped to MongoDB ones.

Related

Spring #Transactional managing entities

I have some uncatchable bug in my work.
For example, I have code that looks like this:
#Entity
public class Message {
#Id
#GeneratedValue(strategy = SEQUENCE, generator = "message_generator")
private long id;
private long massMessageId;
}
public class MessageDTO {
public final long id;
public final long massMessageId;
}
#Transactional
#Service
public class ExtendedMessageService {
private MessageService messageService;
public MessageDTO createMessage(MessageCreateDTO createDTO) {
var messageDTO = messageService.create();
return messageService.linkMassMessage(messageDTO.id, createDTO.massMessageId);
}
}
#Transactional
#Service
public class MessageService {
private final MessageRepository repository;
private final ObjectMapper mapper;
public MessageDTO create() {
var message = new Message();
var savedMessage = repository.save(message);
return mapper.map(savedMessage, MessageDTO.class);
}
public MessageDTO linkMassMessage(long messageId, long massMessageId) {
var message = repository.findById(messageId)
.orElseThrow(() -> new ObjectNotFoundException("Message with id " + id + " was not found"));
return mapper.map(repository.save(message.setMassMessageId(massMessageId)), MessageDTO.class);
}
}
What will happen in this situation? I have some bugs, when repository.findById(id) can't find entity and throws exception.
And i have no reason, why this bug is only on prod (i tried to repeat it on dev and nothing succeeded)
And when i try to find the reason of it, i get a question:
"Can i save entity and get it in one transaction in Spring?"
How saving works
repository.save() doesn't save anything to database, this method puts entity to the session (persistent context) in memory.
flush step — on this step actual SQL insert happens. It can be invoked manually repository.saveAndFlush(), repository.flush(). Hibernate can do flush in the background, before operations that can use saved to the database value, like JPQL statements.
Also flush happens when the end of #Transactional boundary is reached.
What can be an issue
You are using incorrect method. This method from the old version of Spring data and it doesn't perform search in the database. You have to use findById() method instead.
Hibernate: findById vs getbyId
The most simple way, if you want to use id after save — flush the data immediately.
Entity entity = new Entity(some_information);
repository.saveAndFlush(entity);
Entity findedEntity = repository.findById(entity.getId())
.orElseThrow(() -> new RuntimeException("Can't find id=" + entity.getId()));
Hibernate will not necessary perform SQL select to get findedEntity. It can get it from the session, if it happens in the same #Transactional boundaries.
So if the above code resides in the method with #Transaction SQL will not performed. if there is not #Transaction SQL will be performed.
About this question
"Can Spring or Hibernate find not flushed entity in transaction context? Or there are some other ways to do it?"
Hibernate can't find not flushed entity. if id is autogenerated, Hibernate needs to perform SQL INSERT (flush) to get the id from a database. Another option to set up an id manually. Probably in this case it will be possible to get an entity from the persistent context.

Generic Data fetch API based on entity name and its primary key

I'm developing a generic API to fetch data based on the Entity name and its primary key.
URL for get mapping: api/fetch/{id}/data/{entity}
There are many entities present like student, course, instructor, class...
Based on the entity name, the API should return data for that entity by given id in URL.
What should be the best approach using spring boot and JPA?
Trying below, but cannot work when entities are large in number and keep on increasing. Need a generic approach.
#RestController
public class Datacontroller{
#Autowired
CourseRepo courserepo;
#Autowired
Studentrepo studentrepo;
#GetMapping("api/fetch/{id}/data/{entity}")
public <T> T getData(#PathVariable("id") String id, #PathVariable("entity") String entity) {
T l = null;
//depending on entity
if("course".equals(entity)) {
Optional<Course> c = courserepo.findById(id);
l=(T) c.get();
}
if("student".equals(entity)) {
Optional<Student> a = studentrepo.findById(id);
l = (T) a.get();
}
return l;
}
Maybe you should try Spring Data REST. It's a different approach than yours, but it's a Spring project, actively supported and it allows you to directly expose your repositories as REST endpoints.
We can fetch all entities and get entity class from entityName. Once we have class, we can use find method from EntityManager to get the particular record by primary id.
public static Class<?> getEntityClass(EntityManager entityManager, String entityName) {
for (EntityType<?> entity : entityManager.getMetamodel().getEntities()) {
if (entityName.equals(entity.getName())) {
return entity.getJavaType();
}
}
return null;
}

Why can't a column be set to null using JPA?

Let's start with this entity:
#Entity
public class MyEntity {
...
#Column(length = 80)
private String description;
#Column(name = "enum_column", precision = 18)
#Convert(converter = EnumColumnConverter.class)
private MyEnum enumColumn;
...
}
Here, you see two columns that are nullable (in my entity and in the database). The converter replaces the enum with a Long value in the database. A repository class is defined accordingly:
#Repository
public interface MyEntityRepository extends JpaRepository<MyEntity, Long> {}
A DTO is defined from a service package:
public class MyEntityDto {
...
private String description;
private MyEnum enumColumn;
...
}
Mapping between DTOs and entities is done using Dozer. A DTO is modified from a Java FX UI. A service has been defined between UI and persistence to save modified entities.
#Service
#Transactional
public class MyEntityService {
#Autowired MyEntityRepository myEntityRepository;
...
public List<MyEntityDto> save(List<MyEntityDto> dtosToSave) {
List<MyEntityDto> results = Collections.emptyList();
if (dtosToSave != null && !dtosToSave.empty()) {
Iterable<MyEntity> entities = convertDtosWithDozer(dtosToSave);
List<MyEntity> savedEntities = myEntityRepository.saveAll(entities);
results = convertEntitiesWithDozer(savedEntities);
}
return results;
}
From the UI, I modify an existing row where both descriptionand enumColumn are not null. Both values are set to null.
The problem is that none of them is set to null in the database. In the logs, the update request generated by Hibernate does not include these columns. When I debug the code, these columns are null in dtosToSave, entities, savedEntities and results.
I created a unit test for MyEntityRepository where I save an entity with non null description and entityColumn. I reload the entity from the database using the repository to be sure these columns are not null. I set them to null, save the entity, and load it back from the database. Now both columns are indeed null, which is what I've been expecting.
My question: what am I missing here? Why the repository does not save null columns? If I set any non null values, it works perfectly.
Thanks in advance.
UPDATE: could my problem be related to this? Jpa Repository save() doesn't update existing data
You convert your dtos to entities via dozer but as this point entities are still in detached-state.... to update existing entities you first need to load them through database via your repository. Something like repository.findById(Id id);
Then you will get entity in "attached" state and so state transitions(update on fields) will be applied.
During the save() all your entity state transitions will be translated to corresponding DML and your update should work now.
And regarding this statement
I reload the entity from the database using the repository to be sure these columns are not null. I set them to null, save the entity, and load it back from the database. Now both columns are indeed null, which is what I've been expecting.
As you said you reload entity from the database so it works

Update entity in redis with spring-data-redis

I'm currently using Redis (3.2.100) with Spring data redis (1.8.9) and with Jedis connector.
When i use save() function on an existing entity, Redis delete my entity and re create the entity.
In my case i need to keep this existing entity and only update attributes of the entity. (I have another thread which read the same entity at the same time)
In Spring documentation (https://docs.spring.io/spring-data/data-redis/docs/current/reference/html/#redis.repositories.partial-updates), i found the partial update feature. Unfortunately, the example in the documentation use the update() method of RedisTemplate. But this method do not exist.
So did you ever use Spring-data-redis partial update?
There is another method to update entity redis without delete before?
Thanks
To get RedisKeyValueTemplate, you can do:
#Autowired
private RedisKeyValueTemplate redisKVTemplate;
redisKVTemplate.update(entity)
You should use RedisKeyValueTemplate for make partial update.
Well, consider following docs link and also spring data tests (link) actually made 0 contribution to resulting solution.
Consider following entity
#RedisHash(value = "myservice/lastactivity")
#Data
#AllArgsConstructor
#NoArgsConstructor
#Builder
public class LastActivityCacheEntity implements Serializable {
#Id
#Indexed
#Size(max = 50)
private String user;
private long lastLogin;
private long lastProfileChange;
private long lastOperation;
}
Let's assume that:
we don't want to do complex read-write exercise on every update.
entity = lastActivityCacheRepository.findByUser(userId);
lastActivityCacheRepository.save(LastActivityCacheEntity.builder()
.user(entity.getUser())
.lastLogin(entity.getLastLogin())
.lastProfileChange(entity.getLastProfileChange())
.lastOperation(entity.getLastOperation()).build());
what if there would pop up some 100 rows? then on each update entity got to fetched and saved, quite inefficient, but still would work out.
we don't actually want complex exercises with opsForHash + ObjectMapper + configuring beans approach - it's quite hard to implement and maintain (for example link)
So we're about to use something like:
#Autowired
private final RedisKeyValueTemplate redisTemplate;
void partialUpdate(LastActivityCacheEntity update) {
var partialUpdate = PartialUpdate
.newPartialUpdate(update.getUser(), LastActivityCacheEntity.class);
if (update.getLastLogin() > 0)
partialUpdate.set("lastlastLogin", update.getLastLogin());
if (update.getLastProfileChange() > 0)
partialUpdate.set("lastProfileChange", update.getLastProfileChange());
if (update.getLastOperation() > 0)
partialUpdate.set("lastOperation", update.getLastOperation());
redisTemplate.update(partialUpdate);
}
and the thing is - it doesn't really work for this case.
That is, values getting updated but you can not query new property later on via repository entity lookup: certain lastActivityCacheRepository.findAll() will return unchanged properties.
Here's the solution:
LastActivityCacheRepository.java:
#Repository
public interface LastActivityCacheRepository extends CrudRepository<LastActivityCacheEntity, String>, LastActivityCacheRepositoryCustom {
Optional<LastActivityCacheEntity> findByUser(String user);
}
LastActivityCacheRepositoryCustom.java:
public interface LastActivityCacheRepositoryCustom {
void updateEntry(String userId, String key, long date);
}
LastActivityCacheRepositoryCustomImpl.java
#Repository
public class LastActivityCacheRepositoryCustomImpl implements LastActivityCacheRepositoryCustom {
#Autowired
private final RedisKeyValueTemplate redisKeyValueTemplate;
#Override
public void updateEntry(String userId, String key, long date) {
redisKeyValueTemplate.update(new PartialUpdate<>(userId, LastActivityCacheEntity.class)
.set(key, date));
}
}
And finally working sample:
void partialUpdate(LastActivityCacheEntity update) {
if ((lastActivityCacheRepository.findByUser(update.getUser()).isEmpty())) {
lastActivityCacheRepository.save(LastActivityCacheEntity.builder().user(update.getUser()).build());
}
if (update.getLastLogin() > 0) {
lastActivityCacheRepository.updateEntry(update.getUser(),
"lastlastLogin",
update.getLastLogin());
}
if (update.getLastProfileChange() > 0) {
lastActivityCacheRepository.updateEntry(update.getUser(),
"lastProfileChange",
update.getLastProfileChange());
}
if (update.getLastOperation() > 0) {
lastActivityCacheRepository.updateEntry(update.getUser(),
"lastOperation",
update.getLastOperation());
}
all credits to Chris Richardson and his src
If you don't want to type your field names as strings in the updateEntry method, you can use use the lombok annotation on your entity class #FieldNameConstants. This creates field name constants for you and then you can access your field names like this:
...
if (update.getLastOperation() > 0) {
lastActivityCacheRepository.updateEntry(update.getUser(),
LastActivityCache.Fields.lastOperation, // <- instead of "lastOperation"
update.getLastOperation());
...
This makes refactoring the field names more bug-proof.

Reusing Persistence Context in JPA

i was just working on #EmbededId code, i want to do an auto increment before the entity is persisted, this is want to do without use of #GeneratedValue and an identity column,
below is the table with composite id,
create table TBL_EMPLOYEE_002(
ID integer,
COUNTRY varchar(50),
NAME varchar(50),
constraint PK_EMP_00240 primary key(ID,COUNTRY)
)
this is the code for Entity mapping,
#Entity
#Table(name="TBL_EMPLOYEE_002")
public class EmployeeEntitySix implements Serializable{
// contructor's
#EmbeddedId
private EmployeeIdTwo id;
#Column(name="NAME")
private String employeeName;
// getters and setter's
#PrePersist
public void incId(){
EntityManager em = null;
Query q = null;
EntityManagerFactory emf = null;
try{
emf = Persistence.createEntityManagerFactory("forPractise");
em = emf.createEntityManager();
q = em.createQuery("select max(e.id.employeeId) from EmployeeEntitySix e");
List list = q.getResultList();
Integer i = (list != null && list.size() > 0) ? Integer.valueOf(list.get(0).toString()) : 0;
this.getId().setEmployeeId(++i);
}catch(Exception e){
System.out.println("EXCETION WHILE INCREASING COUNTER...");
e.printStackTrace();
}finally{
if(em != null && em.isOpen()){
em.close();
}
if(getEmf() != null && getEmf().isOpen()){
getEmf().close();
}
}
}
This is the composite id mapping,
#Embeddable
public class EmployeeIdTwo implements Serializable{
#Column(name="ID")
private Integer employeeId;
#Column(name="COUNTRY",length=50)
private String empCountry;
// getters and setters
}
this code is of my main method, this main method is in some other class,
public static void main(String [] args){
EntityManagerFactory emf = null;
EntityManager em = null;
EntityTransaction tx = null;
try{
emf = Persistence.createEntityManagerFactory("forPractise");
em = emf.createEntityManager();
tx = em.getTransaction();
tx.begin();
EmployeeEntitySix employee = new EmployeeEntitySix(new EmployeeIdTwo("ZIMBABWE"), "Henry Olanga");
em.persist(employee);
....
}
Now the above code runs fine,
whenever i persist the entity "EmployeeEntitySix", the method annotated with #PerPersist runs, which will first fetch the max id, increments its, set it into the id in the embeded entity and persist the entity.
Now my question is,
I am creating EntityManagerFactory twice,
first in the main method,
second time in the #PrePersist method in entity EmployeeEntitySix. So whether i can use the first Entitymanagerfactory created in main method in the entity EmployeeEntitySix while pre-persist, or else whether i can reuse the entitymanager created in first time in main method in the #PrePersist method in entity.
Just for information, I am using plain java environment, I am not using a Java EE container.
Hibernate by default tries to persist all fields of an entity class or embedded id, including the field emf, but it does not know how to persist a field of the type EntityManagerFactory.
Of course it does not make sense to persist an EntityManagerFactory. You could mark the field as #Transient to prevent it from being persisted, but then you are just going to face different problems.
The injection of an EntityManagerFactory with a #PersistenceUnit annotation only works on CDI Beans and EJBs in applications that run on a Java EE-compliant application server. As you are using a main method, I assume that your example is a simple JSE program.
Furthermore you should not access EntityManagers in lifecycle callback methods such as #PrePersist. A quote from the JPA Specification (JSR 317: JavaTM Persistence API, Version 2.0):
In general, the lifecycle method of a portable application should not invoke EntityManager
or Query operations, access other entity instances, or modify relationships within the
same persistence context. A lifecycle callback method may modify the non-relationship
state of the entity on which it is invoked.
I suggest that you keep the EntityManagerFactory out of your embedded id class and also get rid of the incId-Method. Instead you could execute the query to determine the new employeeId in your main method, before calling persist. This works fine as long as only one instance of the program works with the database. When there are multiple programs trying to insert new employees there could be race conditions where the two programs try to insert the same id.
In order to prevent this you can use a database sequence to generate the employeeId, with the annotations #GeneratedValue and #SequenceGenerator. You find more information about id generation here: http://en.wikibooks.org/wiki/Java_Persistence/Identity_and_Sequencing#Sequencing

Categories