How can I get an insert lock on a table when using Spring Hibernate CrudRepository Dao pattern? - spring

I'm trying to write a default method in a CrudRepository dao that:
Locks a table such that inserts are prevented (but reads and updates would be okay)
Looks for an existing entry using another method in the dao.
If found, returns the found entry.
If not found, inserts the provided entry, and returns it.
Unlocks the table.
I've looked into using #Lock(LockModeType.PESSIMISTIC_WRITE) on the method, but since it doesn't have an associated #Query annotation, I don't think it does anything.
I also tried creating lock and unlock methods in the dao:
#Query(value = "LOCK TABLES mail WRITE", nativeQuery = true)
void lockTableForWriting();
#Query(value = "UNLOCK TABLES", nativeQuery = true)
void unlockTable();
But those threw a SQLGrammerException on LOCK and UNLOCK.
I can't get a row lock because the row doesn't exist yet. Or if it does exist, I'm not going to update anything, I'm just going to not insert anything and move on to other things.
There are other cases where I want multiple records saved with the same transaction id, so I can't just make the column unique and try/catch the save.
In my service layer, I do some lookup attempts and short-circuit when possible, but there's still a chance that multiple near-simultaneous calls might result in two attempts to insert the same data.
It needs to be handled at the db level since there will be multiple instances of the service running. So two competing calls might be on different machines that talk to the same database.
Here's my repository:
#Repository
public interface MailDao extends CrudRepository<Mail, Long> {
default Mail safeSave(Mail mail) {
return Optional.ofNullable(findByTransactionId(mail.getTransactionId()))
.orElseGet(() -> save(mail));
}
default Mail findByTransactionId(String transactionId) {
final List<Mail> mails = findAllByTransactionId(transactionId);
// Snipped code that selects a single entry to return
// If one can't be found, null is returned.
}
#Query(value = "SELECT m " +
" FROM Mail m " +
" WHERE m.transactionId = :transactionId ")
List<Mail> findAllByTransactionId(#Param("transactionId") String transactionId);
}
And here's a bit of what the Mail model looks like:
#Entity
#Table(name = "mail")
public class Mail implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "mail_id", unique = true, nullable = false)
private Long mailId;
#Column(name = "transaction_id", nullable = false)
private String transactionId;
// Snipped code for other parameters,
// constructors, getters and setters.
}
Here's the general idea of the service method that would be calling safeSave.
#Service
public class MailServiceImpl implements MailService {
#Inject private final MailDao mailDao;
// Snipped injected other stuff
#Override
public void saveMailInfo(final String transactionId) {
Objects.requireNonNull(transactionId, "null transactionId passed to saveMailInfo");
if (mailDao.findByTransactionId(transactionId) != null) {
return;
}
// Use one of the injected things to do some lookup stuff
// using external services
if (mailDao.findByTransactionId(transactionId) != null) {
return;
}
// Use another one of the injected things to do more lookup
if (/* we've got everything we need */) {
final Mail mail = new Mail();
mail.setTransactionId(transactionId);
// Snipped code to set the rest of the stuff
mailDao.safeSave(mail);
}
}
}
What I'm trying to prevent is two nearly-simultaneous calls to saveMailInfo causing duplicate records in the database.
The underlying database is MySQL, but we also use an H2 in-memory database for unit tests, and will be changing to PostgreSQL soon, so it'd be nice to have something db-agnostic.
Update 1:
I tried creating a custom impl:
public interface MailDaoCustom {
Mail safeSave(Mail mail);
}
Updated MailDao to implement it:
public interface MailDao extends CrudRepository<Mail, Long>, MailDaoCustom
Then the impl:
public class MailDaoImpl implements MailDaoCustom {
#Autowired private MailDao mailDao;
#Autowired private EntityManager em;
public Mail safeSave(Mail mail) {
Query lockTable = em.createNativeQuery("LOCK TABLES mail WRITE");
Query unlockTable = em.createNativeQuery("UNLOCK TABLES");
try {
lockTable.executeUpdate();
return Optional.ofNullable(mailDao.findByTransactionId(mail.getTransactionId()))
.orElseGet(() -> mailDao.save(mail));
} finally {
unlockTable.executeUpdate();
}
}
}
This is the error I got when testing the above. It's the same as when I tried making the methods in the dao with the queries to lock and unlock:
SQL Error: 42000, SQLState: 42000
Syntax error in SQL statement "LOCK[*] TABLES MAIL WRITE "; SQL statement:
LOCK TABLES mail WRITE [42000-168]
My testing has been done using unit tests that use an H2 in-memory database, though, and not MySQL. It looks like H2 might not really have table locking, though.
Is there maybe another solution to my issue that doesn't involve table locking?
Update 2: I went with an INSERT ... WHERE NOT EXISTS query in a custom repo similar to my first update:
public interface MailDaoCustom {
Mail safeSave(Mail mail);
}
Updated MailDao to implement it:
public interface MailDao extends CrudRepository<Mail, Long>, MailDaoCustom
And then the impl looks like this:
public class MailDaoImpl implements MailDaoCustom {
#Autowired private MailDao dao;
#Autowired private EntityManager em;
public Mail safeSave(Mail mail) {
// Store a new mail record only if one doesn't already exist.
Query uniqueInsert = em.createNativeQuery(
"INSERT INTO mail " +
" (transaction_id, ...) " +
"SELECT :transactionId, ... " +
" WHERE NOT EXISTS (SELECT 1 FROM mail " +
" WHERE transaction_id = :transactionId) ");
uniqueInsert.setParameter("transactionId", mail.getTransactionId());
// Snipped setting of the rest of the parameters in the query
uniqueInsert.executeUpdate();
// Now go get the record
Mail entry = dao.findByTransactionId(mail.getTransactionId());
// Detatch the entry so that we can attach the provided mail object later.
em.detach(entry);
// Copy all the data from the db entry into the one provided to this method
mail.setMailId(entry.getMailId());
mail.setTransactionId(entry.getTransactionId());
// Snipped setting of the rest of the parameters in the provided mail object
// Attach the provided object to the entity manager just like the save() method would.
em.merge(mail);
return mail;
}
}
It's not quite as clean as I was hoping, and I fear I may have made some mistake about something that's kind of hidden and I won't find out until things blow up. But we'll see.

I went with a INSERT INTO ... WHERE NOT EXISTS query, and a custom repo. This is listed above under update 2, but I'm putting it here too so that it's easier to find.
public interface MailDaoCustom {
Mail safeSave(Mail mail);
}
Updated MailDao to implement it:
public interface MailDao extends CrudRepository<Mail, Long>, MailDaoCustom
And then the impl looks like this:
public class MailDaoImpl implements MailDaoCustom {
#Autowired private MailDao dao;
#Autowired private EntityManager em;
public Mail safeSave(Mail mail) {
// Store a new mail record only if one doesn't already exist.
Query uniqueInsert = em.createNativeQuery(
"INSERT INTO mail " +
" (transaction_id, ...) " +
"SELECT :transactionId, ... " +
" WHERE NOT EXISTS (SELECT 1 FROM mail " +
" WHERE transaction_id = :transactionId) ");
uniqueInsert.setParameter("transactionId", mail.getTransactionId());
// Snipped setting of the rest of the parameters in the query
uniqueInsert.executeUpdate();
// Now go get the record
Mail entry = dao.findByTransactionId(mail.getTransactionId());
// Detach the entry so that we can attach the provided mail object later.
em.detach(entry);
// Copy all the data from the db entry into the one provided to this method
mail.setMailId(entry.getMailId());
mail.setTransactionId(entry.getTransactionId());
// Snipped setting of the rest of the parameters in the provided mail object
// Attach the provided object to the entity manager just like the save() method would.
em.merge(mail);
return mail;
}
}

Related

How to do findById for autogenerated id

I have a scenario where I am consuming an event and saving the details in the DB. Now the record that is being stored in the database has the id field autogenerated #GeneratedValue(strategy = GenerationType.IDENTITY).
In my test case I need to check if data is getting stored in the DB or not and is as per expectation.
But I am not sure how will I do findById() of SpringBoot Crud/JPA Repository since I do not know what value got generated.
Any help would be appreciated.
Take a look at save method from CrudRepository interface. Spring executes this method in transaction and after its completion Hibernate will generate identifier in returned entity.
Suppose your entity and repository looks as following:
....
public class SomeEntity {
#Id
#GeneratedValue
private Long id;
private String name;
public SomeEntity(String name){
this.name = name;
}
....
}
public interface SomeRepository extends CrudRepository<SomeEntity, Long> {
}
After saving entity:
SomeEntity someEntity = someRepository.save(new SomeEntity("Some entity"));
someEntity.getId() will contain actual record id which can be used further in your tests.
I think you are looking for annotation #DirtiesContext .
It is a Test annotation which indicates that the ApplicationContext associated with a test is dirty and should therefore be closed and removed from the context cache. - javadoc
Read Section 9.3.4 - Here
Check - Example ans below as well:
#Test
#DirtiesContext
public void save_basic() {
// get a course
Course course = courseJpaRepository.findById(10001L);
assertEquals("JPA ", course.getName());
// update details
course.setName("JPA - Updated");
courseJpaRepository.saveOrUpdate(course);
// check the value
Course course1 = courseJpaRepository.findById(10001L);
assertEquals("JPA - Updated", course1.getName());
}
BTW - how you can get the id : simply via getter method from the return type of save
EmployeeDetails employeeDetails = emaployeeService.saveEmployeeDetails(employee);
int temp = employeeDetails.getID()
Related Post : Here

Challenge Persisting Complex Entity using Spring Data JDBC

Considering the complexities involved in JPA we are planning to use Spring Data JDBC for our entities for its simplicity. Below is the sample structure and we have up to 6 child entities. We are able to successfully insert the data into various of these entities with proper foreign key mappings.
Challenge:- We have a workflow process outside of this application that periodically updates the "requestStatus" in the "Request" entity and this is the only field that gets updated after the Request is created. As with spring data JDBC, during the update it deletes all referenced entities and recreates(inserts) it again. This is kind of a heavy operation considering 6 child entities. Are there any workaround or suggestion in terms of how to handle these scenarios
#Table("Request")
public class Request {
private String requestId; // generated in the Before Save Listener .
private String requestStatus;
#Column("requestId")
private ChildEntity1 childEntity1;
public void addChildEntity1(ChildEntity1 childEntityobj) {
this.childEntity1 = childEntityobj;
}
}
#Table("Child_Entity1")
public class ChildEntity1 {
private String entity1Id; // Auto increment on DB
private String name;
private String SSN;
private String requestId;
#MappedCollection(column = "entity1Id", keyColumn = "entity2Id")
private ArrayList<ChildEntity2> childEntity2List = new ArrayList<ChildEntity2>();
#MappedCollection(column = "entity1Id", keyColumn = "entity3Id")
private ArrayList<ChildEntity3> childEntity3List = new ArrayList<ChildEntity3>();
public void addChildEntity2(ChildEntity2 childEntity2obj) {
childEntity2List.add(childEntity2obj);
}
public void addChildEntity3(ChildEntity3 childEntity3obj) {
childEntity3List.add(childEntity3obj);
}
}
#Table("Child_Entity2")
public class ChildEntity2 {
private String entity2Id; // Auto increment on DB
private String partyTypeCode;
private String requestId;
}
#Table(Child_Entity3)
public class ChildEntity3 {
private String entity3Id; // Auto increment on DB
private String PhoneCode;
private String requestId;
}
#Test
public void createandsaveRequest() {
Request newRequest = createRequest(); // using builder to build the object
newRequest.addChildEntity1(createChildEntity1());
newRequest.getChildEntity1().addChildEntity2(createChildEntity2());
newRequest.getChildEntity1().addChildEntity3(createChildEntity3());
requestRepository.save(newRequest);
}
The approach you describe in your comment:
Have a dedicated method performing exactly that update-statement is the right way to do this.
You should be aware though that this does ignore optimistic locking.
So there is a risk that the following might happen
Thread/Session 1: reads an aggregate.
Thread/Session 2: updates a single field as per your question.
Thread/Session 1: writes the aggregate, possibly with other changes, overwriting the change made by Session 2.
To avoid this or similar problems you need to
check that the version of the aggregate root is unchanged from when you loaded it, in order to guarantee that the method doesn't write conflicting changes.
increment the version in order to guarantee that nothing else overwrites the changes made in this method.
This might mean that you need two or more SQL statements which probably means you have to fallback even more to a full custom method where you implement this, probably using an injected JdbcTemplate.

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.

JPA Hibernate Spring Repository ensures transaction completes on save?

I am creating a simple spring application which is supposed to book seats in a seminar. Lets say Booking class looks like this
#Entity
#Table(name = "bookings")
#IdClass(BookingId.class)
public class Booking{
#Id
private Long seminarId;
#Id
private String seatNo;
// .. other fields like perticipant info
// .. getter setters
}
of course the BookingId class:
public class BookingId implements Serializable{
private static final long serialVersionUID = 1L;
private Long seminarId;
private String seatNo;
// .. constructors, getters, setters
}
And I have a repository
#Repository
public interface BookingsRepository extends JpaRepository<Booking, BookingId>{
}
in the controller when a booking request arrives I first check if a booking with same seminer id and seat number already exists, if it doesn't exist I create one
#RequestMapping(method = RequestMethod.POST)
public ResponseEntity<BaseCrudResponse> createNewBooking(#Valid #RequestBody NewBookingDao newBookingDao, BindingResult bindingResult){
logger.debug("Request for a new booking");
// .. some other stuffs
Booking newBooking = new Booking();
newBooking.setSeminarId(newBookingDao.getSeminarId());
newBooking.setSeatNumber(newBookingDao.getSeatNumber());
// .. set other fields
Booking existing = bookingsRepository.findOne(new BookingId(newBooking.getSeminarId(), newBooking.getSeatNumber());
if (existing == null)
bookingsRepository.save(newBooking);
return new ResponseEntity<>(new BaseCrudResponse(0), HttpStatus.CREATED);
}
return new ResponseEntity<>(response, HttpStatus.BAD_REQUEST);
}
Now what will happen if the save method of the repository didn't finish commiting transaction and another request already gets past the existence check ? There might be incorrect booking (the last commit will override the previous). Is this scenario likely to happen ? Will the repository ensures that it completes the transaction before another save call ?
Also is there any way to tell Jpa to throw some exception (for IntegrityConstraintException if the composite key (in this case seminerId and seatNumber) already exists ? Now in the present setting its just updating the row.
You can use javax.persistence.LockModeType.PESSIMISTIC_WRITE so other transactions except the one that got the lock cannot update the entity.
If you use spring-data > 1.6 you can annotate the repository method with #Lock :
interface BookingsRepository extends Repository<Booking, Long> {
#Lock(LockModeType.PESSIMISTIC_WRITE)
Booking findOne(Long id);
}
For sure you need to handle the locking exception that may be thron in the controller.

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.

Resources