Unique Constraint error thrown at the end of #Transactional block - java

Suppose I have a user class:
#Entity
#Data
#Builder
public class User {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
Long userKey;
#Column(unique = true)
String someId;
String name;
}
And it's corresponding service
#Component
#Slf4j
public class UserService {
#Autowired
UserRepository repository;
#Transactional
public User createUserWithId(String name, String id) {
User userToAdd = User.builder()
.name(name)
.someId(id)
.build();
repository.save(userToAdd);
log.info("No issue in saving");
//some more code
return userToAdd;
}
}
As you can see that I have a unique constraint on someId field in User class but when I execute the method createUserWithId with a value in someId which is already present in DB, I'd expect to get an error on the line containing repository.save() and the code after it to not be executed. But the code after it is getting executed and I'm getting an exception at the end of the transactional block. My question is why this is happening and what are the exceptions which I would generally get when interacting with the repository object ( like in this case repository.save ) and which type of exceptions will I get at the end of transactional block ?
PS I am calling the UserService from inside a simple controller and I have created an empty UserRepository which just extends CrudRepository. Both of which I have left out from the question for brevity but let me know if adding them here would make sense.
EDIT 1: Adding user repository as per request in comments
#Repository
public interface UserRepository extends CrudRepository<User, Long> {
}

The error happens in the interceptor because right before committing the transaction, Hibernate needs to flush pending changes to the database. During that flush, the database exception happens. You can flush manually by calling saveAndFlush on the repository.

Uniqe error happens when duplicate .
spring has database error helper class that you can catch db exceptions on controller layer passed by #transactional to controller.
} catch (DataAccessException ex) {
or
} catch (DataIntegrityViolationException ex) {
in case the database connector has standard exception throw support.
in your case I think you missed
#Transactional(readOnly = false)

When we call
repository.save(obj);
hibernate kept this entity in memory, the Entity will persist at the of the method in the Transaction.
One more way to do such kind of operation, first you should try to fetch result on id
repository.findById(id)
And check whether it is null or not and accordingly perform save operation.

Related

Spring Data findById returns cached value instead of database one

I am preparing notification system for API which I've build before.
Basically I have an aspect which listens on projectRepository.save method. What I want to achieve is check project status in an entity which is a parameter for save method with original status from database record. What I have notice is that when I search for the DB record by id it returns cached value so it is always the same as the object which is in save method even if database still have old value. Can I force Spring Data Jpa to return database record instead of cached entity?
#Aspect
#Component
#RequiredArgsConstructor
public class NotificationAspect {
private final UserService userService;
private final ProjectRepository projectRepository;
private final NotificationService notificationService;
#Pointcut("execution(* *com.stars.domain.project.ProjectRepository.save(..))")
public void projectSavePointcut() {}
#Before("projectSavePointcut()")
public void sendNotificationOnStatusChange(JoinPoint joinPoint) {
if(joinPoint.getArgs().length > 0 && joinPoint.getArgs()[0] instanceof Project) {
Project projectToUpdate = (Project) joinPoint.getArgs()[0];
Optional<Project> oldProject = projectRepository.findById(projectToUpdate.getProjectId());
if(oldProject.isPresent() && !oldProject.get().getStatus().equals(projectToUpdate.getStatus())) {
notificationService.saveNotification(
MessageFormat.format("Project: {} status has been changed from: {} to: {}",
projectToUpdate.getName(),
oldProject.get().getStatus(),
projectToUpdate.getStatus()),
List.of(userService.getUser(projectToUpdate.getCreatedBy())));
}
}
}
}
This line always returns true even if database record has different value.
oldProject.get().getStatus().equals(projectToUpdate.getStatus())
I can think of two ways.
First, if you're interested only in status field, you can create a custom native query in a repository, which will bypass EntityManager, for example like this:
#Query("SELECT p.status FROM projects p WHERE p.id = :id", nativeQuery = true)
String getProjectStatusById(#Param("id") String projectId);
Second looks like a bad idea, but it should work - you can make the entity manager's cache detach all managed entities, so it will be forced to make a DB call again.
For this inject EntityManager in your aspect bean and call its .clear() method right before calling projectRepository.findById method.

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.

For spring-batch transaction is not getting rolled back after exception

I am working on a spring-batch, where after reader and processor, writer is responsible to populate data to DB. Writer is calling Service which internally calls DAO layer. In method insertToDB() if some exception occurs the transaction is not being rolled back. PSB my code.
public class MyWriter{
#Autowired
private MyService myService;
#Override
public void write(List<? extends MyBO> list) {
try{
for(MyBO bo: list){
myService.insert(bo);
}
}
catch(Exception e){
log.error("Cant write to DB")
}
}
public class MyService{
#Autowired
private TableOneDAO tableOneDao;
#Autowired
private TableTwoDAO tableTwoDAO;
#Autowired
private TableThreeDAO tableThreeDAO;
public void insert(MyBO bo){
try{
// do other stuff of processing bo and create entity
MyEntityTableOne myentity1 = getEntityT1(bo);
MyEntityTableTwo myentity2 = getEntityT2(bo);
MyEntityTableThree myentity3 = getEntityT3(bo);
insertToDB(myEntity1,myEntity2,myEntity3);
}
catch(Exception e){
log.error("Error occured.");
throw new MyException("Error Blah blah occured");
}
}
#Transactional(value = "txn1")
public void insertToDB(MyEntityTableOne entity1, MyEntityTableTwo entity2, MyEntityTableThree entity3) {
try{
tableOneDao.insert(entity1);
tableTwoDAO.insert(entity2);
tableThreeDAO.insert(entity3);
}
catch(Exception e){
log.error("Error occured during insert to DB");
throw new MyException("Error Blah blah occured during DB insert");
}
}
The code goes to the catch block, but doesn't rollback records. If some error occurs during insert of Table2 then entry for Table1 is not rolled-back. And if occurs during table3 insertion then table1 and table2 records are not rolled-back.
If I move the #Transactional annotation to insert() method it works fine. What is root cause of this issue. What I have to do if I want to have transaction only on insertToDB() method.
I am trying to make it simple: To support #Transactional spring wraps the implementing class into a so called proxy and surrounds the method call / class with the transactional logic.
Now you are calling the #Transactional annotated method within the same class. Therefore the proxy is not invoked and the transactional does not work. When moving the annotation to your insert method you are invoking the method from outside of the class which means you invoke the method against the proxy.
Thats a limitation of Spring AOP (?) I think.
You can do something like following to achieve what you want:
public class MyService{
#Ressource
private MyService self;
...
self.insertToDB(myEntity1,myEntity2,myEntity3)
Your item writer will be already called in a transaction driven by Spring Batch and that you can configure at the step level by providing the transaction manager and transaction attributes. So there is no need to use #Transactional in the downstream service used by the writer.
You need to remove that annotation from MyService and it should work as expected.

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.

#Transaction annotated method does not save data into a database

In my unit tests I want to persist some entities and test their retrieval from the database. They were not being saved and I figured out that when the test method was also annotated with #Transaction, anything that happened inside it did not get persisted, even though the method finished without an error.
I had previously encountered a LazyInitializationException when messing with a many-to-many lazy-loaded association and annotating the method with #Transaction seemed to fix the issue, that's why I have been using it.
What could be the cause why the entities don't get saved? There is no reason for the transaction to be rolled back, since it does not fail.
Code of related classes:
#Test
#Transactional
public void plainPersistence() throws NullParameterException {
User user = userHelper.createUser("User1", "password", null, null);
Assert.assertNotNull(userDAO.findByUsername("User1"));
}
userHelper:
#Service
public class UserHelper {
#Autowired
private UserDAO userDAO;
public User createUser(...) throws NullParameterException {
User newUser = new User(username, ...);
userDAO.save(newUser);
return newUser;
}
UserDAO's save() method subsequently calls save() on UserRepository:
#Repository
public interface UserRepository extends CrudRepository<User, Long> {
public User findByUsername(String username);
}
Since you're likely using Spring test, you should note that they are configured so that the default behaviour is to rollback the changes. To change this you should annotate your test classes with, if you are using Spring < 4.2
#TransactionConfiguration(defaultRollback = false)
otherwise, annotate the class with #Rollback(value = false)

Categories