Spring & Hibernate - to get persisteed object state changed - java

In my webapp, Spring transaction and Hibernate session API are used.
Please see my service and DAO classes and usage below;
BizCustomerService
#Service
#Transactional(propagation = Propagation.REQUIRED)
public class BizCustomerService {
#Autowired
CustomerService customerService;
public void createCustomer(Customer cus) {
//some business logic codes here
customerService.createCustomer(cus);
//***the problem is here, changing the state of 'cus' object
//it is necessary code according to business logic
if (<some-check-meet>)
cus.setWebAccount(new WebAccount("something", "something"));
}
}
CustomerService
#Service
#Transactional(propagation = Propagation.REQUIRED)
#Scope(proxyMode = ScopedProxyMode.TARGET_CLASS)
public class CustomerService {
#Autowired
CustomerDAO customerDao;
public Long createCustomer(Customer cus) {
//some code goes here
customerDao.save();
}
}
CustomerDAO
#Repository
public class CustomerDAO {
#Autowired
private SessionFactory sessionFactory;
private Session getSession() {
return sessionFactory.getCurrentSession();
}
public Long save(Customer customer) {
//* the old code
//return (Long) getSession().save(customer);
//[START] new code to change
Long id = (Long) getSession().save(customer);
//1. here using 'customer' object need to do other DB insert/update table functions
//2. if those operation are failed or success, as usual, they are under a transaction boundary
//3. lets say example private method
doSomeInsertUpdate(customer);
//[END] new code to change
return id;
}
//do other insert/update operations
private void doSomeInsertUpdate(customer) {
//when check webAccount, it is NULL
if (customer.getWebAccount() != null) {
//to do something
}
}
}
Customer
#Entity
#Table(name = "CUSTOMER")
public class Customer {
//other relationships and fields
#OneToOne(fetch = FetchType.LAZY, mappedBy = "customer")
#Cascade({CascadeType.ALL})
public WebAccount getWebAccount() {
return this.webAccount;
}
}
In the above code, customer is created in BizCustomerService then may change the state of related WebAccount after persisted through DAO. And when the transaction is committed, a new Customer and related WebAccount object are persisted to DB. This is normal I know.
The problem is; in CustomerDAO#save() >> doSomeInsertUpdate() method, 'webAccount' is NULL and the value is not set yet at that time.
EDIT : Left to mention that, it is restricted and don't want to change the code at BizCustomerService and CustomerService because there can be many invocations to DAO methods it can impact alot. So want to change only at DAO level.
So my question is how can I access WebAccount object in doSomeInsertUpdate() method? Any Hibernate usage needed?
Thanks in advance!!

not sure what you're expecting, but I think there is no magic here. If you want a WebAccount to be != null, you'll have to explicitly make one and save it to the DB.
WebAccount wb = new WebAccount();
getSession().save(wb);
customer.setWebAccount(wb);

Related

Custom Repository using Entity Manager in Spring Boot

I observed that the .save() method executes an extra SELECT query to check whether the corresponding record already exists when the corresponding ID is not a AUTO INCREMENT one.
I tried to implement a repository for this kind of situation that will be extended by many JpaRepository interfaces which will be used across different stateless services and I would like to know if my code is safe - race conditions wise - accross multiple requests as I am not that comfortable using the EntityManager yet.
User Entity :
public class User {
#Id
#Column(name = "username", nullable = false, length = 45)
private String username;
#Column(name = "password", nullable = false, length = 256)
private String password;
}
Solution 1 :
public interface SimpleRepository<T> {
void persist(T entity);
}
public class SimpleRepositoryImpl<T> implements SimpleRepository<T> {
#PersistenceContext
EntityManager entityManager;
#Transactional
#Override
public void persist(T entity) {
entityManager.persist(entity);
}
}
User Repository :
public interface UserRepository extends JpaRepository<User, String>, SimpleRepository<User> {}
User Service :
#Service
#AllArgsConstructor
public class UserService {
private final UserRepository userRepository;
public void createUser(User user) {
this.userRepository.persist(user);
}
}
The same implementation will be followed across many different JPA Repositories and Services in the application.
If the solution above is not safe how about this one?
#Service
public class PersistenceService {
#PersistenceContext
private EntityManager entityManager;
#Transactional
public <T> void persist(T entity) {
entityManager.persist(entity);
}
}
Which will turn my UserService and every other Service that is in need of the same functionality to :
#Service
#AllArgsConstructor
public class UserService {
private final PersistenceService persistenceService;
public void createUser(User user) {
this.persistenceService.persist(user);
}
}

How to Map a Java Entity to Multiple MongoDB Collections in Spring Data?

Currently, we're looking for a solution to save the following User entity into multiple MongoDB collections at the same time (i.e. db_users and on db_users_legacy). Both collections are in the same database.
Please don't ask me the reason why I need to save in two collections. It is a business requirement.
#Document(collection = "db_users")
#Data
#NoArgsConstructor
#AllArgsConstructor
public class User {
#Id
private String id;
private String name;
private String website;
private String name;
private String email;
}
And my SpringBoot application configuration goes as;
#Configuration
public class ApplicationConfig {
#Bean
public MongoTemplate mongoTemplate(MongoDbFactory factory){
MongoTemplate template = new MongoTemplate(factory);
template.setWriteConcern(WriteConcern.ACKNOWLEDGED);
retu,rn template;
}
}
Currently my repository looks as this. And saving works perfectly fine. How can I same this document in two different collections?
#Repository
public class UserRepositoryImpl implements UserRepository {
private MongoTemplate mongoTemplate;
public UserRepositoryImpl(MongoTemplate mongoTemplate) {
this.mongoTemplate = mongoTemplate;
}
#Override
public void save(User user) {
mongoTemplate.save(user);
}
}
Can anyone suggest the best option to deal with this, please?
I suggest using MongoTemplate's the other overloaded save method.
#Override
public void save(User user) {
mongoTemplate.save(user, "db_users");
mongoTemplate.save(user, "db_users_legacy");
}
This can be used to save same object to multiple collections.
From docs,
You can customize this by providing a different collection name using the #Document annotation. You can also override the collection name by providing your own collection name as the last parameter for the selected MongoTemplate method calls.
So it doesn't matter the collection name specifically provided in #Document, you can always override it using MongoTemplate.

Design suggession on Spring component and multiple repositories

I have an API component class and each of its business methods need different repositories. This is what I have for example:
#Component
#Transactional (propagation = Propagation.SUPPORTS,
rollbackForClassName="java.lang.Throwable")
#EnableTransactionManagement
public class TransactionApi
{
#Autowired
private TransactionRepository repo;
#Autowired
private ProductRepository prodRepo;
#Autowired
private CustomerRepository custRepo;
#Autowired
private OrderRepository orderRepo;
#Autowired
private ContactRepository contactRepo;
#Autowired
private ShippingPrefsRepository shipPrefsRepo;
#Transactional (readOnly=true)
public Transaction getTransactionDetails(String transactionId)
{
return repo.findOne(transactionId);
}
#Transactional (readOnly=true)
public Product getProductDetails(String productId)
{
return prodRepo.findOne(productId);
}
#Transactional (readOnly=true)
public Customer getCustomerDetails(String customerId)
{
return custRepo.findOne(customerId);
}
public Order createOrder(Order order)
{
Order saved = orderRepo.save(order);
return saved;
}
public Contact createContact(Contact contact)
{
Contact saved = contactRepo.save(contact);
return saved;
}
public ShippingPreference createContact(ShippingPreference shipPref)
{
ShippingPreference saved = shipPrefsRepo.save(shipPref);
return saved;
}
Looking at above design, i have a lot of repositories used in one class and so not all are used each time this class is used. So i think initializing all of them upfront would be an overkill (I assume). I am worried that this might slow down my application or use lot of resources (memory). I will probably will have another 20 repositories when I am done with this class.
My question is am I designing it correctly as it should be done? Or is there better way of doing it. Loading too many repositories into memory when each time just one of those are being used, isn't it inefficient way of doing it? Any suggestions/advice are appreciated.

Spring JPA / Hibernate transaction force insert instead of update

Edited. Whilst extending the base repository class and adding an insert method would work an more elegant solution appears to be implementing Persistable in the entities. See Possible Solution 2
I'm creating a service using springframework.data.jpa with Hibernate as the ORM using JpaTransactionManager.
following the basis of the tutorial here.
http://www.petrikainulainen.net/spring-data-jpa-tutorial/
My entity repositories extend org.springframework.data.repository.CrudRepository
I'm working with a legacy database which uses meaningful primary keys rather then auto generated id's
This situation shouldn't really occur, but I came across it due to a bug in testing. Order table has a meaningful key of OrderNumber (M000001 etc). The primary key value is generated in code and assigned to the object prior to save. The legacy database does not use auto-generated ID keys.
I have a transaction which is creating a new order. Due to a bug, my code generated an order number which already existed in the database (M000001)
Performing a repository.save caused the existing order to be updated. What I want is to force an Insert and to fail the transaction due to duplicate primary key.
I could create an Insert method in every repository which performs a find prior to performing a save and failing if the row exists. Some entities have composite primary keys with a OrderLinePK object so I can't use the base spring FindOne(ID id) method
Is there a clean way of doing this in spring JPA?
I previously created a test service without jpa repository using spring/Hibernate and my own base repository. I implemented an Insert method and a Save method as follows.
This seemed to work OK.
The save method using getSession().saveOrUpdate gave what I'm experiencing now with an existing row being updated.
The insert method using getSession().save failed with duplicate primary key as I want.
#Override
public Order save(Order bean) {
getSession().saveOrUpdate(bean);
return bean;
}
#Override
public Order insert(Order bean) {
getSession().save(bean);
return bean;
}
Possible solution 1
Based on chapter 1.3.2 of the spring docs here
http://docs.spring.io/spring-data/jpa/docs/1.4.1.RELEASE/reference/html/repositories.html
Probably not the most efficient as we're doing an additional retrieval to check the existence of the row prior to insert, but it's primary key.
Extend the repository to add an insert method in addition to save. This is the first cut.
I'm having to pass the key into the insert as well as the entity. Can I avoid this ?
I don't actually want the data returned. the entitymanager doesn't have an exists method (does exists just do a count(*) to check existence of a row?)
import java.io.Serializable;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.repository.NoRepositoryBean;
/**
*
* #author Martins
*/
#NoRepositoryBean
public interface IBaseRepository <T, ID extends Serializable> extends JpaRepository<T, ID> {
void insert(T entity, ID id);
}
Implementation : Custom repository base class.
Note : A custom exception type will be created if I go down this route..
import java.io.Serializable;
import javax.persistence.EntityManager;
import org.springframework.data.jpa.repository.support.JpaEntityInformation;
import org.springframework.data.jpa.repository.support.SimpleJpaRepository;
import org.springframework.transaction.annotation.Transactional;
public class BaseRepositoryImpl<T, ID extends Serializable>
extends SimpleJpaRepository<T, ID> implements IBaseRepository<T, ID> {
private final EntityManager entityManager;
public BaseRepositoryImpl(Class<T> domainClass, EntityManager em) {
super(domainClass, em);
this.entityManager = em;
}
public BaseRepositoryImpl(JpaEntityInformation<T, ?> entityInformation, EntityManager entityManager) {
super (entityInformation, entityManager);
this.entityManager = entityManager;
}
#Transactional
public void insert(T entity, ID id) {
T exists = entityManager.find(this.getDomainClass(),id);
if (exists == null) {
entityManager.persist(entity);
}
else
throw(new IllegalStateException("duplicate"));
}
}
A custom repository factory bean
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.support.JpaRepositoryFactory;
import org.springframework.data.jpa.repository.support.JpaRepositoryFactoryBean;
import org.springframework.data.repository.core.RepositoryMetadata;
import org.springframework.data.repository.core.support.RepositoryFactorySupport;
import javax.persistence.EntityManager;
import java.io.Serializable;
/**
* This factory bean replaces the default implementation of the repository interface
*/
public class BaseRepositoryFactoryBean<R extends JpaRepository<T, I>, T, I extends Serializable>
extends JpaRepositoryFactoryBean<R, T, I> {
protected RepositoryFactorySupport createRepositoryFactory(EntityManager entityManager) {
return new BaseRepositoryFactory(entityManager);
}
private static class BaseRepositoryFactory<T, I extends Serializable> extends JpaRepositoryFactory {
private EntityManager entityManager;
public BaseRepositoryFactory(EntityManager entityManager) {
super(entityManager);
this.entityManager = entityManager;
}
protected Object getTargetRepository(RepositoryMetadata metadata) {
return new BaseRepositoryImpl<T, I>((Class<T>) metadata.getDomainType(), entityManager);
}
protected Class<?> getRepositoryBaseClass(RepositoryMetadata metadata) {
// The RepositoryMetadata can be safely ignored, it is used by the JpaRepositoryFactory
//to check for QueryDslJpaRepository's which is out of scope.
return IBaseRepository.class;
}
}
}
Finally wire up the custom repository base class in the configuration
// Define this class as a Spring configuration class
#Configuration
// Enable Spring/jpa transaction management.
#EnableTransactionManagement
#EnableJpaRepositories(basePackages = {"com.savant.test.spring.donorservicejpa.dao.repository"},
repositoryBaseClass = com.savant.test.spring.donorservicejpa.dao.repository.BaseRepositoryImpl.class)
Possible solution 2
Following the suggestion made by patrykos91
Implement the Persistable interface for the entities and override the isNew()
A base entity class to manage the callback methods to set the persisted flag
import java.io.Serializable;
import javax.persistence.MappedSuperclass;
import javax.persistence.PostLoad;
import javax.persistence.PostPersist;
import javax.persistence.PostUpdate;
#MappedSuperclass
public abstract class BaseEntity implements Serializable{
protected transient boolean persisted;
#PostLoad
public void postLoad() {
this.persisted = true;
}
#PostUpdate
public void postUpdate() {
this.persisted = true;
}
#PostPersist
public void postPersist() {
this.persisted = true;
}
}
Then each entity must then implement the isNew() and getID()
import java.io.Serializable;
import javax.persistence.Column;
import javax.persistence.EmbeddedId;
import javax.persistence.Entity;
import javax.persistence.Table;
import javax.xml.bind.annotation.XmlRootElement;
import org.springframework.data.domain.Persistable;
#Entity
#Table(name = "MTHSEQ")
#XmlRootElement
public class Sequence extends BaseEntity implements Serializable, Persistable<SequencePK> {
private static final long serialVersionUID = 1L;
#EmbeddedId
protected SequencePK sequencePK;
#Column(name = "NEXTSEQ")
private Integer nextseq;
public Sequence() {
}
#Override
public boolean isNew() {
return !persisted;
}
#Override
public SequencePK getId() {
return this.sequencePK;
}
public Sequence(SequencePK sequencePK) {
this.sequencePK = sequencePK;
}
public Sequence(String mthkey, Character centre) {
this.sequencePK = new SequencePK(mthkey, centre);
}
public SequencePK getSequencePK() {
return sequencePK;
}
public void setSequencePK(SequencePK sequencePK) {
this.sequencePK = sequencePK;
}
public Integer getNextseq() {
return nextseq;
}
public void setNextseq(Integer nextseq) {
this.nextseq = nextseq;
}
#Override
public int hashCode() {
int hash = 0;
hash += (sequencePK != null ? sequencePK.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 Sequence)) {
return false;
}
Sequence other = (Sequence) object;
if ((this.sequencePK == null && other.sequencePK != null) || (this.sequencePK != null && !this.sequencePK.equals(other.sequencePK))) {
return false;
}
return true;
}
#Override
public String toString() {
return "com.savant.test.spring.donorservice.core.entity.Sequence[ sequencePK=" + sequencePK + " ]";
}
}
It would be nice to abstract out the isNew() but I don't think I can. The getId can't as entities have different Id's, as you can see this one has composite PK.
I never did that before, but a little hack, would maybe do the job.
There is a Persistable interface for the entities. It has a method boolean isNew() that when implemented will be used to "assess" if the Entity is new or not in the database. Base on that decision, EntityManager should decide to call .merge() or .persist() on that entity, after You call .save() from Repository.
Going that way, if You implement isNew() to always return true, the .persist() should be called no mater what, and error should be thrown after.
Correct me If I'm wrong. Unfortunately I can't test it on a live code right now.
Documentation about Persistable: http://docs.spring.io/spring-data/commons/docs/current/api/org/springframework/data/domain/Persistable.html
Why not create a clone object which clones everything except your primary keys and then save this cloned object.
Since the PK will not be present, an insert happens, instead of an update
Does this help?
Set updatable = false on your PK column definition. Example:
#Id
#GeneratedValue
#Column(name = “id”, updatable = false, nullable = false)
private Long id;
Setting your id non updatable will stop JPA from doing updates on your primary key, so thing about it.
One option that worked for me, although isn't particularly elegant, but is at least very explicit: Define your own insert method on your JpaRepository and then call it from your code.
public interface MyRepository extends JpaRepository<MyRecord, Integer> {
....
#Modifying(clearAutomatically = true)
#Transactional
#Query(value = "INSERT INTO my_table (field1, field2) "
+ "VALUES (:#{#c.field1}, :#{#c.field2})", nativeQuery = true)
public void insert(#Param("c") MyRecord c);
}
And then in the code doing the insert:
final var newRecord = MyRecord.builder()
.field1(...)
....
.build();
try {
myRepository.insert(newRecord);
} catch (DataIntegrityViolationException e) {
....
}

Spring InvalidDataAccessApiUsageException in async method using repository

in a RestController:
#RestController
#RequestMapping("/apks")
public class ApkController {
#Inject
DecompiledApkRepository decompiledApkRepository;
#Autowired
DecompileService decompileService;
#RequestMapping(method = RequestMethod.POST)
public ResponseEntity<?> createFromJson(#RequestBody Apk apk) {
....
DecompiledApk decompiledApk = new DecompiledApk(apk, apkFile, apk.getMd5Hash());
decompileService.decompile(decompiledApk, apk.getMd5Hash(), decompiledApkRepository);
} catch (IOException |InterruptedException e) {
e.printStackTrace();
}
return new ResponseEntity<>(null, responseHeaders, HttpStatus.CREATED);
}
The DecompiledApk entity:
#Entity
#Table(name = "decompiled_apks")
public class DecompiledApk {
#Id
#GeneratedValue
#JsonIgnore
private Long id;
#MapsId
#OneToOne(optional=false)
private Apk apk;
#Column(unique = true)
private URI decompiledFolder;
#Transient
#JsonIgnore
private File inputApk;
// all public getters/setters, package empty constructor and public full constructor
The DecompiledApkRepository:
#Repository
public interface DecompiledApkRepository extends CrudRepository<DecompiledApk, Long> {
DecompiledApk findByApk_md5Hash(String md5Hash);
}
And here, the problem is in the async method in the DecompileService:
#Service
public class DecompileService {
private static final String DEC_FOLDER = "/tmp/decompiled/";
#Async
public Future<Void> decompile(DecompiledApk decompiledApk, String md5Hash, DecompiledApkRepository decompiledApkRepository) throws InterruptedException {
/*
...
*/
decompiledApk.setDecompiledFolder(URI.create(outputFolder));
System.err.println("---start-->"+Thread.currentThread().getName());
decompiledApkRepository.save(decompiledApk);
System.err.println("---end-->"+Thread.currentThread().getName());
return new AsyncResult<>(null);
}
The instruction:
decompiledApkRepository.save(decompiledApk);
throw:
org.springframework.dao.InvalidDataAccessApiUsageException: detached entity passed to persist: com.xxx.domain.Apk; nested exception is org.hibernate.PersistentObjectException: detached entity passed to persist: com.xxx.domain.Apk
But if I remove the #Async annotation it works without problem!
Any ideas?
Do you need more details?
Although the method has been marked as asynchronous, there are no spring transaction markers , suspect that is causing is the problem.
Try adding Spring transaction :
org.springframework.transaction.annotation
#Transactional
Hopefully that will solve the issue.
Spring's transaction context is preserved using ThreadLocals. This means that your SessionFactory is only available to the thread dispatching your request thus, if you create a new thread, you will get a null and a corresponding exception.
What your #Async method does is use a TaskExecutor to run your method in another thread. So the problem described above is happening with your service.
According to link

Categories