When should I use entityManager.flush() if i am already using #Transactional - java

I am using JPA in Spring Boot Application.
I am only getting starting to use It and I have some questions.
My DAO code is the following:
#Transactional
public class DatabaseUnitDao implements IDatabaseUnitDao {
#PersistenceContext
private EntityManager entityManager;
#Override
public void create(Unit unit) {
final String CREATE_UNIT =
"CREATE TABLE " + unit.getName() + " (id VARCHAR(255) PRIMARY KEY NOT NULL, value text NOT NULL)";
entityManager.persist(unit); // add an info about unit in the general table
entityManager.createNativeQuery(CREATE_UNIT).executeUpdate(); // create table for this units
}
}
1.Should I use flush() in this case?
2.Is It enough to just annotate DAO class with #Transactional?
Some resources tell that it's needed to use #EnableTransactionManagement to use #Transactional.

No. JPA provider has to do it for you at the end of the transactional method's invocation.
Yes. Spring Boot enables transaction management by default (with proxyTargetClass = true)

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.

Prevent hibernate entity changes from being persisted

I am updating my application from Spring Boot 1.4.5 / Hibernate 4.3.5 to Spring Boot 2.0.9 / Hibernate 5.2.18 and code that used to work in the previous configuration is no longer working.
The scenario is as follows:
Start a transaction by entering a method annotated with #Transactional
Hydrate the entity
Change the entity
Make another query
Detect a problem. As a result of this problem, determine that changes should not persist.
Evict the entity
Exit the method / transaction
With Hibernate 4.3.5, calling entityManager.detach() would prevent the changes from being persisted. However, with Hibernate 5.2.18, I'm finding that changes are persisted even with this call. I have also tried to evict() from the session and I have tried to clear() all entities from the session (just to see what would happen).
So I ask - is it possible to discard entity changes in Hibernate 5.2.18 the way that I was able to do in Hibernate 4.3.5?
The relevant code is below...
#Entity
public class Agreement {
private Long agreementId;
private Integer agreementStateId;
#Id
#Column(name = "agreement_id")
public Long getAgreementId() {
return agreementId;
}
public void setAgreementId(Long agreementId) {
this.agreementId = agreementId;
}
#Basic
#Column(name = "agreement_state_id", nullable = false)
public Integer getAgreementStateId() {
return agreementStateId;
}
public void setAgreementStateId(Integer agreementStateId) {
this.agreementStateId = agreementStateId;
}
}
#Component
public class Repo1 {
#PersistenceContext(unitName = "rights")
private EntityManager entityManager;
public void evict(Object entity) {
entityManager.detach(entity);
}
public Agreement getAgreement(Long agreementId) {
// Code to get entity is here.
// Agreement with an agreementStateId of 5 is returned.
}
public void anotherQuery() {
// Code to make another query is here.
}
}
#Component
public class Service1 {
#Autowired
Repo1 repo;
#Transactional
public void doSomething() {
Agreement agreement = repo.getAgreement(1L);
// Change agreementStateId. Very simple for purposes of example.
agreement.setAgreementStateId(100);
// Make another query
repo.anotherQuery();
// Detect a problem here. Simplified for purposes of example.
if (agreement.getAgreementStateId() == 100) {
repo.evict(agreement);
}
}
}
I have found the problem and it has nothing to do with evict(). It turns out that an additional query was causing the session to flush prior to the evict() call.
In general, the application uses QueryDSL to make queries. Queries made in this way did not result in the session flushing prior to making a query. However in this case, the query was created via Session.createSQLQuery(). This uses the FlushMode already assigned to the session which was FlushMode.AUTO.
I was able to prevent the flush by calling setHibernateFlushMode(FlushMode.COMMIT) on the query prior to making the query. This causes the session FlushMode to temporarily change until after the query has been run. After that, the evict() call worked as expected.

Is #Transactional support for NamedParameterTemplate.batchUpdate

Is #Transactional support for NamedParameterTemplate.batchUpdate?
If something went wrong during the batch execution, will it rollback as expected? Personally, I am not experienced that. That's why I am asking.
Is there any document to check #Transactional supported methods.
public class JdbcActorDao implements ActorDao {
private NamedParameterTemplate namedParameterJdbcTemplate;
public void setDataSource(DataSource dataSource) {
this.namedParameterJdbcTemplate = new NamedParameterJdbcTemplate(dataSource);
}
#Transactional
public int[] batchUpdate(List<Actor> actors) {
return this.namedParameterJdbcTemplate.batchUpdate(
"update t_actor set first_name = :firstName, last_name = :lastName where id = :id",
SqlParameterSourceUtils.createBatch(actors));
}
// ... additional methods
}
NamedParameterTemplate is just an abstraction around Jdbc. In spring it is the Transaction Manager that is responsible for managing transactions, not that you can not do it via plain JDBC but this is the spring way. Spring uses AOP internaly to inspect the annotated methods and delegates its transaction managment. But this role is separate from the NamedParameterTemplate.
So you can use it freely and annotate your methods as long as they are within a Spring managed component/bean with #Transactional

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

Is it possible to use transactions in Spring AOP advice?

I am trying to implement logging in DB table using Spring AOP. By "logging in table" I mean to write in special log table information about record that was CREATED/UPDATED/DELETED in usual table for domain object.
I wrote some part of the code and all is working good except one thing - when transaction is rolled back then changes in log table still commit successfully. It's strange for me because in my AOP advice the same transaction is using as in my business and DAO layer. (From my AOP advice I called methods of special manager class with Transaction propagation MANDATORY and also I checked transaction name TransactionSynchronizationManager.getCurrentTransactionName() in business layer, dao layer and AOP advice and it is the same).
Does anyone tried to implement similar things in practice? Is it possible to use in AOP advice the same transaction as in the business layer and rollback changes made in AOP advice if some error in business layer occurs?
Thank you in advance for unswers.
EDIT
I want to clarify that problem with rollback occurs only for changes that was made from AOP advice. All changes that is made in DAO layer are rollbacked successfully. I mean that, for example, if some exception is thrown then changes made in DAO layer will be successfully rollbacked, but in log table information will be saved (commited). But I can't understand why it is like that because as I wrote above in AOP advice the same transaction is using.
EDIT 2
I checked with debugger the piece of the code where I am writting to the log table in AOP advice and it seems to me that JdbcTemplate's update method executes outside transaction because changes had been commited to the DB directly after execution of the statement and before transactional method was finished.
EDIT 3
I solved this problem. Actually, that was my stupid fault. I'm using MySQL. After creation of the log table I did't change DB engine and HeidySQL set MyIsam by default. But MyIsam doesn't support transaction so I changed DB engine to InnoDB (as for all other tables) and now all is working perfectly.
Thank you all for help and sorry for disturbing.
If someone is interested, here is simplified example that illustrate my approach.
Consider DAO class that has save method:
#Repository(value="jdbcUserDAO")
#Transactional(propagation=Propagation.SUPPORTS, readOnly=true, rollbackFor=Exception.class)
public class JdbcUserDAO implements UserDAO {
#Autowired
private JdbcTemplate jdbcTemplate;
#LoggedOperation(affectedRows = AffectedRows.ONE, loggedEntityClass = User.class, operationName = OperationName.CREATE)
#Transactional(propagation=Propagation.REQUIRED, readOnly=false, rollbackFor=Exception.class)
#Override
public User save(final User user) {
if (user == null || user.getRole() == null) {
throw new IllegalArgumentException("Input User object or nested Role object should not be null");
}
KeyHolder keyHolder = new GeneratedKeyHolder();
jdbcTemplate.update(new PreparedStatementCreator() {
#Override
public PreparedStatement createPreparedStatement(Connection connection)
throws SQLException {
PreparedStatement ps = connection.prepareStatement(SQL_INSERT_USER, new String[]{"ID"});
ps.setString(1, user.getUsername());
ps.setString(2, user.getPassword());
ps.setString(3, user.getFullName());
ps.setLong(4, user.getRole().getId());
ps.setString(5, user.geteMail());
return ps;
}
}, keyHolder);
user.setId((Long) keyHolder.getKey());
VacationDays vacationDays = user.getVacationDays();
vacationDays.setId(user.getId());
// Create related vacation days record.
vacationDaysDAO.save(vacationDays);
user.setVacationDays(vacationDays);
return user;
}
}
Here is how aspect looks like:
#Component
#Aspect
#Order(2)
public class DBLoggingAspect {
#Autowired
private DBLogManager dbLogManager;
#Around(value = "execution(* com.crediteuropebank.vacationsmanager.server.dao..*.*(..)) " +
"&& #annotation(loggedOperation)", argNames="loggedOperation")
public Object doOperation(final ProceedingJoinPoint joinPoint,
final LoggedOperation loggedOperation) throws Throwable {
Object[] arguments = joinPoint.getArgs();
/*
* This should be called before logging operation.
*/
Object retVal = joinPoint.proceed();
// Execute logging action
dbLogManager.logOperation(arguments,
loggedOperation);
return retVal;
}
}
And here is how my db log manager class LooksLike:
#Component("dbLogManager")
public class DBLogManager {
#Autowired
private JdbcTemplate jdbcTemplate;
#InjectLogger
private Logger logger;
#Transactional(rollbackFor={Exception.class}, propagation=Propagation.MANDATORY, readOnly=false)
public void logOperation(final Object[] inputArguments, final LoggedOperation loggedOperation) {
try {
/*
* Prepare query and array of the arguments
*/
jdbcTemplate.update(insertQuery.toString(),
insertedValues);
} catch (Exception e) {
StringBuilder sb = new StringBuilder();
// Prepare log string
logger.error(sb.toString(), e);
}
}
It could be to do with the order of the advice - you would want your #Transaction related advice to take effect around(or before and after) your logging related advice. If you are using Spring AOP you can probably control it using the order attribute of the advice - give your transaction related advice the highest precedence so that it executes last on the way out.
Nothing to do with AOP, set datasource property autocommit to false like :
<bean id="datasource" ...>
<property name="autoCommit" value="false/>
</bean>
If you are using xml configuration

Categories