Spring JPA: How to check if Entity belongs to current JPA transaction? - spring

I need the ability to check if Entity belongs to the current ongoing transaction. I have a JPA entity cache as a thread-local cache. Almost always when there is single #Transaction there is no issue when a same thread calls multiple times to save on service and NOT nested #Trasaction(Propagation.REQUIRES_NEW) (meaning when you have nested transactions) on same thread it does not work. Is there any way to check if JPA Entity ( MyExpesiveEntity) belongs to the current ongoing transaction?
ThreadLocal<Map<Long,MyExpesiveEntity>> cache = new ThreadLocal<Map<Long,MyExpesiveEntity>>()
#Entity('MyExpesiveEntity')
class MyExpesiveEntity{
}
#Transaction
save(DTO save){
MyExpesiveEntity entity = cache.get(dto.getID());
if(entity == null){
entity = myExpesiveRespository.findById(dto.getID());
cache.get().put(entity.getID(),entity);
}
entity.setXXX(dto.getXXX())
// THIS always works but Sometime when the caller is using
// Propagation.REQUIRES_NEW.
// It does not work when you have nested transactions.It throws duplicated id.
myExpesiveRespository.saveAndFlush(entity);
}

It's Hibernate specific, but yes, this is possible
public boolean containsEntity(EntityManager em, Class<?> entityClass, Object id) {
SessionImplementor session = em.unwrap(SessionImplementor.class);
EntityKey entityKey = session.generateEntityKey((Serializable) id, session.getFactory().getEntityPersister(entityClass.getName()));
PersistenceContext pc = session.getPersistenceContext();
return pc.getEntity(entityKey) != null || pc.getProxy(entityKey) != null;
}

Since the entity is always existing you could simply update based on the ID. See Modifying
#Modifying
#Query("update MyExpesiveEntity expEnt set expEnt.xxxx = ?1 where expEnt.id = ?2")
void setXxxById(String xxxx, Integer id);

Related

Spring boot manually commit transaction

In my Spring boot app I'm deleting and inserting a large amount of data into my MySQL db in a single transaction. Ideally, I want to only commit the results to my database at the end, so all or nothing. I'm running into issues where my deletions will be committed before my insertions, so during that period any calls to the db will return no data (not good). Is there a way to manually commit transaction?
My main logic is:
#Transactional
public void saveParents(List<Parent> parents) {
parentRepo.deleteAllInBatch();
parentRepo.resetAutoIncrement();
//I'm setting the id manually before hand
String sql = "INSERT INTO parent " +
"(id, name, address, number) " +
"VALUES ( ?, ?, ?, ?)";
jdbcTemplate.batchUpdate(sql, new BatchPreparedStatementSetter() {
#Override
public void setValues(PreparedStatement ps, int i) throws SQLException {
Parent parent = parents.get(i);
ps.setInt(1, parent.getId());
ps.setString(2, parent.getName());
ps.setString(3, parent.getAddress());
ps.setString(4, parent.getNumber());
}
#Override
public int getBatchSize() {
return parents.size();
}
});
}
ParentRepo
#Repository
#Transactional
public interface ParentRepo extends JpaRepository<Parent, Integer> {
#Modifying
#Query(
value = "alter table parent auto_increment = 1",
nativeQuery = true
)
void resetAutoIncrement();
}
EDIT:
So I changed
parentRepo.deleteAllInBatch();
parentRepo.resetAutoIncrement();
to
jdbcTemplate.update("DELETE FROM output_stream");
jdbcTemplate.update("alter table output_stream auto_increment = 1");
in order to try avoiding jpa's transaction but each operation seems to be committing separately no matter what I try. I have tried TransactionTemplate and implementing PlatformTransactionManager (seen here) but I can't seem to get these operations to commit together.
EDIT: It seems the issue I was having was with the alter table as it will always commit.
I'm running into issues where my deletions will be committed before my insertions, so during that period any calls to the db will return no data
Did you configure JPA and JDBC to share transactions?
If not, then you're basically using two different mechanisms to access the data (EntityManager and JdbcTempate), each of them maintaining a separate connection to the database. What likely happens is that only EntityManager joins the transaction created by #Transactional; the JdbcTemplate operation executes either without a transaction context (read: in AUTOCOMMIT mode) or in a separate transaction altogether.
See this question. It is a little old, but then again, using JPA and Jdbc together is not exactly a common use case. Also, have a look at the Javadoc for JpaTransactionManager.

Hibernate - Table Locked after update

I'm performing an update via a method using Hibernate and the EntityManager.
This update method is called multiple times (within a loop).
It seems like when I execute it the first time, it locks the table and does not free it.
When trying to update the table via SQL Developer after having closed the application, I see the table is still locked because the update is hanging.
What do you see as a solution to this problem? If you need more information, let me know.
Class
#Repository
#Transactional(propagation = REQUIRES_NEW)
public class YirInfoRepository {
#Autowired
EntityManager entityManager;
#Transactional(propagation = REQUIRES_NEW)
public void setSent(String id) {
String query = "UPDATE_QUERY";
Query nativeQuery = entityManager.createNativeQuery(String.format(query, id));
nativeQuery.executeUpdate();
}
}
UPDATE
After having waited more than one hour, I launched the application again and it worked fine once but now again, it hangs.
UPDATE 2 -- I'll give a maximum bounty to whoever helps me solve this
On another place I use an application managed entity manager and it still gives me the same type of errors.
public void fillYirInfo() {
File inputFile = new File("path");
try (InputStream inputStream = new FileInputStream(inputFile);
BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(inputStream))) {
bufferedReader.lines().skip(1).limit(20).forEach(line -> {
String[] data = line.split(",");
String rnr = data[0];
String linked = data[1];
String email = data.length > 2 ? data[2] : "";
String insuredId = insuredPeopleRepository.getInsuredIdFromNationalId(rnr);
int modifiedCounter = 0;
if (!isNullOrEmpty(insuredId)) {
EntityManager entityManager = emf.createEntityManager();
EntityTransaction transaction = entityManager.getTransaction();
Query nativeQuery = entityManager.createNativeQuery(
"QUERY"
);
transaction.begin();
nativeQuery.executeUpdate();
entityManager.flush();
transaction.commit();
entityManager.close();
}
System.out.println(modifiedCounter + " rows modified");
});
} catch (IOException e) {
e.printStackTrace();
}
}
Try without an update-query:
#Repository
#Transactional(propagation = REQUIRES_NEW)
public class YirInfoRepository {
#Autowired
EntityManager entityManager;
#Transactional(propagation = REQUIRES_NEW)
public void setSent(String id) {
//guessing your class name and method..
final YirInfo yirInfo = entityManager.find(YirInfo.class, id);
yirInfo.setSent();
}
}
Might not be as fast as a single update query, but it's possible to get it reasonably fast, unless the amount of data is huge. This is the preferred way of using Hibernate/JPA, instead of thinking in terms of single values and SQL queries, you work with entities/objects and (sometimes) HQL/JPQL queries.
You are using #Transactional annotation. This means you are using Spring Transaction. Then in your UPDATE 2 you are using transaction by yourself and managed by spring (I guess it's another project or class not managed by Spring).
In any case what I would do is to try to update your records in single spring transaction and I'd not use #Transactional in DAO layer but in service layer. Something like this:
Service layer:
#Service
public class YirInfoService {
#Autowired
YirInfoRepository dao;
#Transactional(propagation = REQUIRES_NEW)
public void setSent(List < String > ids) {
dao.setSents(ids);
}
}
DAO layer:
#Repository
public class YirInfoRepository {
#Autowired
EntityManager entityManager;
//Here you can update by using and IN statement or by doing a cycle
//Let's suppose a bulk operation
public void setSents(List < String > ids) {
String query = "UPDATE_QUERY";
for (int i = 0; i < ids.size(); i++) {
String id = ids.get(i);
Query nativeQuery = entityManager.createNativeQuery(String.format(query, id));
nativeQuery.executeUpdate();
if (i % 20 == 0) {
entityManager.flush();
entityManager.clear();
}
}
}
}
The first thing you have to understand is that for the first example, you are using a native query to update rows in the DB. In this case you are completely skipping Hibernate to do anything for you.
In your second example, you have the same thing, you are updating via an update query. You don't need to flush the entity manager as it's only necessary for transferring the pending changes made to your entity objects within that entity manager.
Plus I don't know how your example works as you are autowiring the entity manager and not using the #PersistenceContext annotation. Make sure you use this one properly because you might have misconfigured the application. Also there is no need to manually create the entity manager when using Spring as it looks in the second example. Just use #PersistenceContext to get an entity manager in your app.
You are also mixing up transaction management. In the first example, it's enough if you put the #Transactional annotation to either of your method or to the class.
For the other example, you are doing manual transaction management which makes no sense in this case. If you are using Spring, you can simply rely on declarative transaction management.
The first thing I'd check here is to integrate datasource-proxy into your connection management and log out how your statements are executed. With this info, you can make sure that the query is sent to the DB side and the DB is executing it very slowly, or you are having a network issue between your app and db.
If you find out that the query is sent properly to the DB, you want to analyze your query, because most probably it's just executed very slowly and needs some optimizations. For this, you can use the Explain plan feature, to find out how your execution plan looks like and then make it faster.

Spring Data repo not returning updated #version from save method

In a Spring Boot 1.3.0 application, I am using Spring Data JPA with Hibernate.
I have this service method:
#Override
#Transactional(readOnly = false)
public BookDto updateBook(BookDto bookDto) {
BookId id = new BookId(bookDto.getId());
if (!exists(id)) {
throw new EntityNotFoundException("Unable to find a book with id " + bookDto.getId());
}
return convertToDto(bookRepository.save(bookDto.convertToBook()));
}
In my entity, I have a #Version field:
#Version
private long version;
(with getter and setter) and I also have this in my DTO (without the #Version annotation on my DTO).
The 'Book' entity coming back from the bookRepository.save() call still has version = 0, instead of 1 as expected. When I do a findAll on the repository afterwards, the version is updated fine. What could be reason for this?
As it is a transaction, commit is happening after return statement. Changes are persisted in database while committing the transaction in this case. As you are not flushing explicitly, changes reflect in database only after commit. And before commit operation you are mapping the current state of entity to DTO.
Solution 1
If your spring repository is extending JpaRepository then you can do the following.
#Override
#Transactional(readOnly = false)
public BookDto updateBook(BookDto bookDto) {
BookId id = new BookId(bookDto.getId());
if (!exists(id)) {
throw new EntityNotFoundException("Unable to find a book with id " + bookDto.getId());
}
return convertToDto(bookRepository.saveAndFlush(bookDto.convertToBook()));
}
Solution 2
Another solution without explicit flushing: move the conversion of entity to dto outside the updateBook() method. Use some other helper function to map entity to dto. Reason is because entities are in sync with database after commit operation if there is no explicit flush. But you are mapping them to DTO before the transaction is committed. So move the entity to DTO mapping to some mapper class or do it in the service layer.
#Override
#Transactional(readOnly = false)
public BookEntity updateBook(BookDto bookDto) {
BookId id = new BookId(bookDto.getId());
if (!exists(id)) {
throw new EntityNotFoundException("Unable to find a book with id " + bookDto.getId());
}
return bookRepository.save(bookDto.convertToBook());
}
// call this method to map book entity to dto
public BookDTO mapToDTO(BookEntity){
return bookDTO;
}

How to handle DataIntegrityVilolationException while saving a list in Spring Data JPA?

I am using Spring Data JPA in a Spring Boot Application, with MYSQL. There I am saving a list of entities with unique constraint over a field. Out of the list of entities, there is one entity that will throw DataIntegrityViolationException due to the unique constraint. I noticed that none of the entities get persisted in that case, even those that does not violate the unique constraint.
What should be the ideal approach in this case so that those entities which do not violate the unique get persisted ?
Of course I can iterate the list and save them one by one. In fact that is what SimpleJpaRepository is doing underneath.
#Transactional
public <S extends T> List<S> save(Iterable<S> entities) {
List<S> result = new ArrayList<S>();
if (entities == null) {
return result;
}
for (S entity : entities) {
result.add(save(entity));
}
return result;
}
My code - Entity :
#Entity
#Table(uniqueConstraints = #UniqueConstraint(columnNames = { "name" }, name = "uq_name"))
public class SampleContent {
#Id
#GeneratedValue
private Long id;
private String name;
//getter setters
}
Repository :
public interface SampleContentRepository extends JpaRepository<SampleContent, Serializable>{
}
JUnit test :
#Test
public void testCreate(){
List<SampleContent> sampleContentList = new ArrayList<>();
SampleContent sampleContent1 = new SampleContent();
sampleContent1.setName("dd");
SampleContent sampleContent2 = new SampleContent();
sampleContent2.setName("Roy");
SampleContent sampleContent3 = new SampleContent();
sampleContent3.setName("xx");
sampleContentList.add(sampleContent1);
sampleContentList.add(sampleContent2);
sampleContentList.add(sampleContent3);
try{
this.sampleContentRepository.save(sampleContentList);
}catch(DataIntegrityViolationException e){
System.err.println("constraint violation!");
}
}
There is an entity with name "Roy" already present in the table. So, the entire transaction fails and #Transactional rolls back.
I think you can use next steps:
Load existing entities from DB into Set
Override equals and hashCode methods based on name
call Set::addAll you antities (or just add them one by one)
save that Set to DB
Maybe it's suboptimal because forces you to make select * query. But I think it's much more effective then saving entities one by one to DB.
Accotding to this article you can use name as your business key, which has lots of benefits.

Spring Boot Data JPA - Modifying update query - Refresh persistence context

I'm working with Spring Boot 1.3.0.M4 and a MySQL database.
I have a problem when using modifying queries, the EntityManager contains outdated entities after the query has executed.
Original JPA Repository:
public interface EmailRepository extends JpaRepository<Email, Long> {
#Transactional
#Modifying
#Query("update Email e set e.active = false where e.active = true and e.expire <= NOW()")
Integer deactivateByExpired();
}
Suppose we have Email [id=1, active=true, expire=2015/01/01] in DB.
After executing:
emailRepository.save(email);
emailRepository.deactivateByExpired();
System.out.println(emailRepository.findOne(1L).isActive()); // prints true!! it should print false
First approach to solve the problem: add clearAutomatically = true
public interface EmailRepository extends JpaRepository<Email, Long> {
#Transactional
#Modifying(clearAutomatically = true)
#Query("update Email e set e.active = false where e.active = true and e.expire <= NOW()")
Integer deactivateByExpired();
}
This approach clears the persistence context not to have outdated values, but it drops all non-flushed changes still pending in the EntityManager. As I use only save() methods and not saveAndFlush() some changes are lost for other entities :(
Second approach to solve the problem: custom implementation for repository
public interface EmailRepository extends JpaRepository<Email, Long>, EmailRepositoryCustom {
}
public interface EmailRepositoryCustom {
Integer deactivateByExpired();
}
public class EmailRepositoryImpl implements EmailRepositoryCustom {
#PersistenceContext
private EntityManager entityManager;
#Transactional
#Override
public Integer deactivateByExpired() {
String hsql = "update Email e set e.active = false where e.active = true and e.expire <= NOW()";
Query query = entityManager.createQuery(hsql);
entityManager.flush();
Integer result = query.executeUpdate();
entityManager.clear();
return result;
}
}
This approach works similar to #Modifying(clearAutomatically = true) but it first forces the EntityManager to flush all changes to DB before executing the update and then it clears the persistence context. This way there won't be outdated entities and all changes will be saved in DB.
I would like to know if there's a better way to execute update statements in JPA without having the issue of the outdated entities and without the manual flush to DB. Perhaps disabling the 2nd level cache? How can I do it in Spring Boot?
Update 2018
Spring Data JPA approved my PR, there's a flushAutomatically option in #Modifying() now.
#Modifying(flushAutomatically = true, clearAutomatically = true)
I know this is not a direct answer to your question, since you already have built a fix and started a pull request on Github. Thank you for that!
But I would like to explain the JPA way you can go. So you would like to change all entities which match a specific criteria and update a value on each. The normal approach is just to load all needed entities:
#Query("SELECT * FROM Email e where e.active = true and e.expire <= NOW()")
List<Email> findExpired();
Then iterate over them and update the values:
for (Email email : findExpired()) {
email.setActive(false);
}
Now hibernate knows all changes and will write them to the database if the transaction is done or you call EntityManager.flush() manually. I know this won't work well if you have a big amount of data entries, since you load all entities into memory. But this is the best way, to keep the hibernate entity cache, 2nd level caches and the database in sync.
Does this answer say "the `#Modifying´ annotation is useless"? No! If you ensure the modified entities are not in your local cache e.g. write-only application, this approach is just the way to go.
And just for the record: you don't need #Transactional on your repository methods.
Just for the record v2: the active column looks as it has a direct dependency to expire. So why not delete active completely and look just on expire in every query?
As klaus-groenbaek said, you can inject EntityManager and use its refresh method :
#Inject
EntityManager entityManager;
...
emailRepository.save(email);
emailRepository.deactivateByExpired();
Email email2 = emailRepository.findOne(1L);
entityManager.refresh(email2);
System.out.println(email2.isActive()); // prints false

Resources