How to solve save/persist entity in concurrent transaction and What is the best approach to handle concurrent requests? - spring

How to solve save/persist entity in concurrent transaction and What is the best approach to handle concurrent requests?
The problem, being encountered is due to concurrency of both the book creation requests, both the instances check that book does not exist in the beginning, both try to create the book in the database, one succeeds other fails.
Let say we have one table with three columns
Table = BOOK
COLUMNS = ID, NAME, AUTHOR
Table also has unique constraint on name and author column.
CONSTRAINT uq_book UNIQUE (NAME, AUTHOR)
#NoRepositoryBean
public class BookRepository implements IBookRepository
{
#Autowired
ISpringJpaBookRepository springJpaBookRepository;
#Override
public Book createBook(Book book) throws Exception {
BookEntity bookEntity = springJpaBookRepository.getByNameAndAuthor(book.getName(), book.getAuthor())
//Create a book entity and save
BookEntity bookEntity = createBook(getBookEntity(book));
bookEntity = springJpaBookRepository.save(bookEntity);
Book bookCreated = getBookFromEntity(bookEntity);
return bookCreated;
}
}
public class BookService implements IBookService
{
#Autowired
IBookRepository bookRepository;
#Override
#Transactional
public Book createBook(Book book) throws Exception {
bookRepository .createBook(Book book);
}
}

Related

Insert nested records to mongo in reactive fashion

Trying to wrap my head around the reactor model and pipeline, I want to insert to mongo a couple of Users, then for each user I would like to insert several (10) Offers
My current implementation include inserting 3 users to the database, block and insert the offers (only for 1 user) in a somewhat backward way, like so
Flux.just(u1, u2, u3).flatMap(u -> reactiveMongoTemplate.insert(u)).blockLast();
Arrays.asList(u1, u2, u3).forEach(user -> {
IntStream.range(0,10).forEach(i -> reactiveMongoTemplate.insert(new Offer(user)).subscribe());
});
The first line run fine, but I get the following exception
java.lang.IllegalStateException: state should be: open
Of course I can bypass this by inserting for each user separately, I don't know why this exception was raised and appreciate an answer about this issue as well
My main question is how to write it in the most reactive way, should I need to block in order to populate the entity Id after insert or there is a better way?
The exact implementation of User and Offer doesn't really matter, it can be a any simple records, but here they are
#Data
#AllArgsConstructor
#NoArgsConstructor
#Document(collection = "users")
public class User extends BaseEntity {
private String name;
}
...
#Data
#Document(collection = "offers")
public class Offer extends BaseEntity {
private String title;
#JsonSerialize(using = ToStringSerializer.class)
private ObjectId user;
public Offer(){
this.title = "some title " + new Random().nextInt(10);
}
public Offer(User user){
this();
this.user = new ObjectId(user.getId());
}
public void setUser(String userId) {
this.user = new ObjectId(userId);
}
}
reactiveMongoTemplate is from spring-boot-starter-data-mongodb-reactive #EnableReactiveMongoRepositories
Thx
Turn out I was pretty close to the correct solution
Flux.just(u1, u2, u3).flatMap(u -> reactiveMongoTemplate.insert(u)).subscribe(u -> {
Flux.range(0,10).flatMap(i -> reactiveMongoTemplate.insert(new Offer(u))).subscribe();
});
now the code is truly reactive and it can be seen on the database as well (records are inserted with random order)

Spring Data JPA repository methods overloading

For example, I have a book JpaRepository. Book has a field called Name, the book repository has a method findOneByName (as the jpa repository method naming convention). But I need two different versions of findOneByName to use in different use cases. One version is lock annotated, the other is lock-free. Like this:
public interface BookRepository extends JpaRepository<BookDAO, Long> {
#Lock(LockModeType.READ)
BookDAO findOneByName( String name );
BookDAO findOneByName( String name );
}
Is it possible to achieve this in Spring? If so, how to distinguish the two methods when calling them. If not, is there another way to do it while still using the Spring JPA repository interfaces (like findOneBy***).
According to reference we can name query methods with these prefixes: find…By, read…By, query…By, count…By, and get…By.
So methods BookDAO findByName(String name) and BookDAO getByName(String name) will do the same thing.
I dont know if it can be done your way. But i would create different methods
public interface BookRepository extends JpaRepository<BookDAO, Long> {
#Lock(LockModeType.READ)
#Query("select b from Book b where b.name = :name")
BookDAO findOneByNameForRead( String name );
BookDAO findOneByName( String name );
}
or you can create methods in your service layer instead of using spring jparepository to handle locking. and use it across where it is needed to be updated, and all read methods marked as #Transactional(readOnly = true)
#PersistenceContext
private EntityManager em;
...
public Book findOneBookForUpdate(String id) {
Book book = em.find(Book.class, id);
if (book != null) {
em.lock(book, LockModeType.PESSIMISTIC_WRITE);
}
return book;
}

How to write a RestController to update a JPA entity from an XML request, the Spring Data JPA way?

I have a database with one table named person:
id | first_name | last_name | date_of_birth
----|------------|-----------|---------------
1 | Tin | Tin | 2000-10-10
There's a JPA entity named Person that maps to this table:
#Entity
#XmlRootElement(name = "person")
#XmlAccessorType(NONE)
public class Person {
#Id
#GeneratedValue
private Long id;
#XmlAttribute(name = "id")
private Long externalId;
#XmlAttribute(name = "first-name")
private String firstName;
#XmlAttribute(name = "last-name")
private String lastName;
#XmlAttribute(name = "dob")
private String dateOfBirth;
// setters and getters
}
The entity is also annotated with JAXB annotations to allow XML payload in
HTTP requests to be mapped to instances of the entity.
I want to implement an endpoint for retrieving and updating an entity with a given id.
According to this answer to a similar question,
all I need to do is to implement the handler method as follows:
#RestController
#RequestMapping(
path = "/persons",
consumes = APPLICATION_XML_VALUE,
produces = APPLICATION_XML_VALUE
)
public class PersonController {
private final PersonRepository personRepository;
#Autowired
public PersonController(final PersonRepository personRepository) {
this.personRepository = personRepository;
}
#PutMapping(value = "/{person}")
public Person savePerson(#ModelAttribute Person person) {
return personRepository.save(person);
}
}
However this is not working as expected as can be verified by the following failing test case:
#RunWith(SpringRunner.class)
#SpringBootTest(webEnvironment = RANDOM_PORT)
public class PersonControllerTest {
#Autowired
private TestRestTemplate restTemplate;
private HttpHeaders headers;
#Before
public void before() {
headers = new HttpHeaders();
headers.setContentType(APPLICATION_XML);
}
// Test fails
#Test
#DirtiesContext
public void testSavePerson() {
final HttpEntity<Object> request = new HttpEntity<>("<person first-name=\"Tin Tin\" last-name=\"Herge\" dob=\"1907-05-22\"></person>", headers);
final ResponseEntity<Person> response = restTemplate.exchange("/persons/1", PUT, request, Person.class, "1");
assertThat(response.getStatusCode(), equalTo(OK));
final Person body = response.getBody();
assertThat(body.getFirstName(), equalTo("Tin Tin")); // Fails
assertThat(body.getLastName(), equalTo("Herge"));
assertThat(body.getDateOfBirth(), equalTo("1907-05-22"));
}
}
The first assertion fails with:
java.lang.AssertionError:
Expected: "Tin Tin"
but: was "Tin"
Expected :Tin Tin
Actual :Tin
In other words:
No server-side exceptions occur (status code is 200)
Spring successfully loads the Person instance with id=1
But its properties do not get updated
Any ideas what am I missing here?
Note 1
The solution provided here is not working.
Note 2
Full working code that demonstrates the problem is provided
here.
More Details
Expected behavior:
Load the Person instance with id=1
Populate the properties of the loaded person entity with the XML payload using Jaxb2RootElementHttpMessageConverter or MappingJackson2XmlHttpMessageConverter
Hand it to the controller's action handler as its person argument
Actual behavior:
The Person instance with id=1 is loaded
The instance's properties are not updated to match the XML in the request payload
Properties of the person instance handed to the controller's action handler method are not updated
this '#PutMapping(value = "/{person}")' brings some magic, because {person} in your case is just '1', but it happens to load it from database and put to ModelAttribute in controller. Whatever you change in test ( it can be even empty) spring will load person from database ( effectively ignoring your input ), you can stop with debugger at the very first line of controller to verify it.
You can work with it this way:
#PutMapping(value = "/{id}")
public Person savePerson(#RequestBody Person person, #PathVariable("id") Long id ) {
Person found = personRepository.findOne(id);
//merge 'found' from database with send person, or just send it with id
//Person merged..
return personRepository.save(merged);
}
wrong mapping in controller
to update entity you need to get it in persisted (managed) state first, then copy desired state on it.
consider introducing DTO for your bussiness objects, as, later, responding with persisted state entities could cause troubles (e.g. undesired lazy collections fetching or entities relations serialization to XML, JSON could cause stackoverflow due to infinite method calls)
Below is simple case of fixing your test:
#PutMapping(value = "/{id}")
public Person savePerson(#PathVariable Long id, #RequestBody Person person) {
Person persisted = personRepository.findOne(id);
if (persisted != null) {
persisted.setFirstName(person.getFirstName());
persisted.setLastName(person.getLastName());
persisted.setDateOfBirth(person.getDateOfBirth());
return persisted;
} else {
return personRepository.save(person);
}
}
Update
#PutMapping(value = "/{person}")
public Person savePerson(#ModelAttribute Person person, #RequestBody Person req) {
person.setFirstName(req.getFirstName());
person.setLastName(req.getLastName());
person.setDateOfBirth(req.getDateOfBirth());
return person;
}
The issue is that when you call personRepository.save(person) your person entity does not have the primary key field(id) and so the database ends up having two records with the new records primary key being generated by the db. The fix will be to create a setter for your id field and use it to set the entity's id before saving it:
#PutMapping(value = "/{id}")
public Person savePerson(#RequestBody Person person, #PathVariable("id") Long id) {
person.setId(id);
return personRepository.save(person);
}
Also, like has been suggested by #freakman you should use #RequestBody to capture the raw json/xml and transform it to a domain model. Also, if you don't want to create a setter for your primary key field, another option may be to support an update operation based on any other unique field (like externalId) and call that instead.
For updating any entity the load and save must be in same Transaction,else it will create new one on save() call,or will throw duplicate primary key constraint violation Exception.
To update any we need to put entity ,load()/find() and save() in same transaction, or write JPQL UPDATE query in #Repository class,and annotate that method with #Modifying .
#Modifying annotation will not fire additional select query to load entity object to update it,rather presumes that there must be a record in DB with input pk,which needs to update.

Why does JPA modifying query require #Transactional annotation?

Given the code below, why is it that when I call PersonService.updateAccountMembership from a controller I need to have the #Transactional annotation on the #Modifying query (AccountRepo.updateMembership)? Isn't having the #Transactional annotation on the service call that calls the modifying query sufficient (AccountService.updateMembership)?
The code below would break if I remove the #Transactional annotation on the modifying query with the following exception:
javax.persistence.TransactionRequiredException: Executing an update/delete query
Service:
#Service
public class PersonService{
#Autowired
PersonRepo personRepo;
#Autowired
AccountService accountService;
public Person updateAccountMembership(Person person, int membership){
person = this.save(person);
accountService.updateMembership(person, membership);
}
#Transactional
public Person save(Person person){
return personRepo.save(person);
}
}
Account Service:
#Service
public class AccountService {
#Autowired
AccountRepo accountRepo;
#Transactional
public void updateMembership(Person person, int membership){
accountRepo.updateMembership(person, membership);
}
}
Account Repo:
public class AccountRepo extends JpaRepository<Account,Integer> {
#Transactional //WHY IS THIS REQUIRED????????
#Modifying
#Query("update .........")
void updateMembership(#Param("person") Person person, #Param("memb") int membership);
}
Sure
You have two kinds of queries in sql. Read-Queries and Write-Queries.
A database can have multiple clients. What if two clients update the gender of a person at the same time? The last one wins!
This is an example using two methods: void buy() and void pay().
Client A read next empty invoice number 0000001
Client B read next empty invoice number 0000001
Client A change invoice0000001-payer to Max
Client A store invoice 0000001
Client B change invoice0000001-payer to Tom
Client B store invoice 0000001 <<--- crash! Already in use!
Problem: Max buyed it, but Tom payed it.
If you use transactions you can bind step 5 with step 6. If Step 6 failed, Step 5 is rolled back.
Your database across-the-board requires transations. You can not modify data without.

Using oneToMany relation, but saving data in individual tables at different point of time

I am working on a Spring-MVC application which has 2 tables in database and 2 domain classes. Class Person has oneTOMany relation with class Notes. I would like to add Person and notes both in database. So I googled, to find out many MVC based examples for the same problem. However they seem to assume a few things :
Data is being added in a static manner by the developer, mostly through Static void main() or another class.
Data regarding all the classes which are related is added altogether, eg : Table A has oneToMany relation, so the code will add data for both the tables in one class or one jsp file.
Other frameworks like Spring-Security at play(This point is understood).
So basically, similar examples with different names and developers is what I found. My problem is :
I don't have static void main, don't intend to use it.
I am adding data through HTML page wrapped inside JSP page.
I or the user will first register through the register form, just login later and then add notes, so I am not adding data for both tables at same time. (I have to believe this is possible by Hibernate)
Error :
org.hibernate.TransientObjectException: object references an unsaved transient instance - save the transient instance before flushing: com.journaldev.spring.model.Person
org.hibernate.engine.internal.ForeignKeys.getEntityIdentifierIfNotUnsaved(ForeignKeys.java:294)
org.hibernate.type.EntityType.getIdentifier(EntityType.java:537)
org.hibernate.type.ManyToOneType.isDirty(ManyToOneType.java:311)
org.hibernate.type.ManyToOneType.isDirty(ManyToOneType.java:321)
org.hibernate.type.TypeHelper.findDirty(TypeHelper.java:294)
Person Model :
#Entity
#Table(name="person")
public class Person implements UserDetails{
private static final GrantedAuthority USER_AUTH = new SimpleGrantedAuthority("ROLE_USER");
#Id
#Column(name="id")
#GeneratedValue(strategy = GenerationType.SEQUENCE,generator = "person_seq_gen")
#SequenceGenerator(name = "person_seq_gen",sequenceName = "person_seq")
private int id;
#OneToMany(cascade = CascadeType.ALL,mappedBy = "person1")
private Set<Notes> notes1;
public Set<Notes> getNotes1() {
return notes1;
}
public void setNotes1(Set<Notes> notes1) {
this.notes1 = notes1;
}
Notes model :
#Entity
#Table(name="note")
public class Notes {
#Id
#Column(name="noteid")
#GeneratedValue(strategy = GenerationType.SEQUENCE,generator = "note_gen")
#SequenceGenerator(name = "note_gen",sequenceName = "note_seq")
private int noteId;
#ManyToOne
#JoinColumn(name = "id")
private Person person1;
public Person getPerson1() {
return person1;
}
public void setPerson1(Person person1) {
this.person1 = person1;
}
NotesDAOImpl :
#Transactional
#Repository
public class NotesDAOImpl implements NotesDAO{
private SessionFactory sessionFactory;
public void setSessionFactory(SessionFactory sf){
this.sessionFactory = sf;
}
#Override
public void addNote(Notes notes, int id) {
Session session = this.sessionFactory.getCurrentSession();
session.save(notes);
}
SQL schema :
CREATE TABLE public.person (
id INTEGER NOT NULL,
firstname VARCHAR,
username VARCHAR,
password VARCHAR,
CONSTRAINT personid PRIMARY KEY (id)
);
CREATE TABLE public.note (
noteid INTEGER NOT NULL,
sectionid INTEGER,
canvasid INTEGER,
text VARCHAR,
notecolor VARCHAR,
noteheadline VARCHAR,
id INTEGER NOT NULL,
CONSTRAINT noteid PRIMARY KEY (noteid)
);
ALTER TABLE public.note ADD CONSTRAINT user_note_fk
FOREIGN KEY (id)
REFERENCES public.person (id)
ON DELETE NO ACTION
ON UPDATE NO ACTION
NOT DEFERRABLE;
Btw, the id in addNote method is just me checking if SpringSecurity is actually sending userid, and has properly loggedin, debug purpose.
So, I am unable to add notes once user is logged in, what am I doing wrong? Or this is not possible with Hibernate. In that case, let me find a gun to shoot myself.. :P
Your code will try to save notes. But these notes will not be linked to any Person. You have to do below sequence of operation.
Find the logged in person or the person for which you want to save the notes.
Create notes object which will be in transient state.
Attach notes to the person.
If it is bidirectional relationaship, then person to notes.
Below is the code template.
#Transactional
#Repository
public class NotesDAOImpl implements NotesDAO{
private SessionFactory sessionFactory;
public void setSessionFactory(SessionFactory sf){
this.sessionFactory = sf;
}
#Override
public void addNote(Notes notes, int id) {
Session session = this.sessionFactory.getCurrentSession();
Person person = getPerson(); // this method should get logged in person or the person for whom you want to save the notes.
if (person.getNotes() == null) {
Set<Note> notes = new HashSet<Note>();
person.setNotes(notes);
}
person.getNotes().add(note);
note.setPerson(person); // If bidirectional relationship.
session.update(person); // if update does not work, try merge();
}
Also make sure you have cascade type set to MERGE in person entity on notes field.
Note: Above code is just example from your code and may have some compilation error. please correct according to your requirement.

Resources