Data base pessimistic locks with Spring data JPA (Hibernate under the hood) - spring

I need some help with pessimistic entity locking. I'm using PostgreSQL and Spring data JPA (hibernate 5 under the hood) in my application. So, I want to show a task I've faced with.
I have some user accounts with money:
#lombok.Data //Used to generate getters and setters
#Entity
class AccountEntity {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id;
private Long balance;
}
And payments, that allows to transfer money from one account to another
#lombok.Data
#Entity
class PaymentEntity {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id;
private Long amount;
#OneToOne
#JoinColumn(name="account_from_id")
private AccountEntity accountFrom;
#OneToOne
#JoinColumn(name="account_to_id")
private AccountEntity accountTo;
}
Repositories for both:
interface AccountRepository extends JpaRepository<AccountEntity, Integer> {}
interface PaymentRepository extends JpaRepository<PaymentEntity, Integer> {}
And a service where money transfer performed:
interface PaymentService {
PaymentEntity create(PaymentEntity paymentEntity);
}
#Service
class PaymentServiceImpl implements PaymentService {
#Autowired
private AccountRepository accountRepository;
#Autowired
private PaymentRepository paymentRepository;
#Transactional
#Override
public PaymentEntity create(PaymentEntity payment) {
AccountEntity from = accountRepository.getOne(payment.getAccountFrom().getId()); //want to lock account here
AccountEntity to = accountRepository.getOne(payment.getAccountTo().getId()); //want to lock account here
Long newFromBalance = from.getBalance() - payment.getAmount();
Long newToBalance = to.getBalance() + payment.getAmount();
if (newFromBalance < 0)
throw new RuntimeException("Not enough money for payment");
from.setBalance(newFromBalance);
to.setBalance(newToBalance);
PaymentEntity result = paymentRepository.save(payment); //create payment
accountRepository.save(from); //update account
accountRepository.save(to); //update account
return result; //want to unlock both accounts here
}
}
So, as you can see from code, I want to lock two accounts, involved to the money transfer at the beginning of the transaction (which will guarantee, that they both can't be changed before the transaction commits, and each new payment will get updated balance of the accounts). At the end of the transaction I want to unlock them both.
I've read documentation, and as I and as I understood, for do that I need to update my AccountRepository:
interface AccountRepository extends JpaRepository<AccountEntity, Integer> {
#Lock(LockModeType.PESSIMISTIC_WRITE)
AccountEntity save(AccountEntity account);
}
The code above must lock from and to accounts at the beginning of the transaction and automatically unlock them at the end. But I still have some questions:
In this case data base locks will be used (not the application one on the java side), because I use multiple instances of the application, that works with the same database. Isn't it?
Annotation #Lock(LockModeType.PESSIMISTIC_WRITE) can be applied only to repository method (not to the service one). Isn't it?
#Lock will lock all entities in a transaction, for which select was performed (from and to accounts in my case). Isn't it?
All locked entities will be unlocked automatically when the transaction commits or rollbacks. Isn't it?
Each payment, which will use account, locked with pessimistic write lock will wait until account will be updated and lock will be released. Isn't it?
I will be very thankful if you provide links to official documentation or just some site, where I can discover this topic by myself.

Related

Update millions of database row using Spring Data JPA

I'm wondering on which is the best for this
Here is my Entity:
#Entity
#Data
public class User {
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String firstName;
private String middleName;
private String lastName;
private Status status;
private String statusRemarks;
}
Option 1 Direct bulk update in UserRepository:
Does this will affect my database performance if the users to be update will reach to millions in number?
public interface UserRepository extends CrudRepository<User, Long> {
#Query("UPDATE u FROM User u set u.status=:status, u.statusRemarks=:statusRemarks where u.status in :statuses")
void bulkUpdateByStatuses(Status status,String statusRemarks,Status... statuses);
}
Option 2 will fetch the users by status and will update it one by one like this:
I'm pretty sure this will affect the performance of the MS due to the memory usage
public void bulkUpdateUserByStatuses(final UserBulkUpdateDto userbulkUpdateDto){
List<User> toUpdateUsers = userRepository.findByStatuses(userbulkUpdateDto.getStatuses())
for(final User user: toUpdateUsers){
user.setStatus(userbulkUpdateDto.getNewStatus());
user.setStatusRemarks(userbulkUpdateDto.getStatusRemarks());
userRepository.save(user);
}
}
}
Absolutely option 1.
Both options read the affected data, modify it and write it back.
The option 2 on top of that reads and writes the data across the network and turns it into java objects.
So it will be slower and probably even put a higher load on the database.
If the load still becomes to high for the database you should consider breaking the update in smaller ones, for example by limiting to a range of ids at a time.

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.

Spring JPA one to many denormalized count field

I have two entities, Books and Comments, in a one to many relationship (one book can have many comments). I want to be able to list books and number of comments about a book. I want it denormalized, meaning the books entity will have a counter that has number of comments for that book, and it will be updated every time a comment is entered (just playing with the concept, no need to discuss about the need of denormalizing here).
I think (correct me if I am wrong) this could be easily done with a trigger in the database (whenever a new comment is created, update a counter in the books table to the corresponding bookId), but for the sake of learning I want to do it through JPA, if it makes sense.
What I have so far: //omitted some annotations, just general info
Boks entity:
#Entity
public class Books {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
private String title;
private String author;
private Long numComments;
// getters and setters...
}
Comments entity:
#Entity
public class Comments {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
private String comment;
private Long authorId;
private Long bookId;
// getters and setters...
}
Books repository: I added here a query to perform the update
/**
* Spring Data JPA repository for the Books entity.
*/
public interface BooksRepository extends JpaRepository<Books,Long> {
#Modifying
#Query("UPDATE Books v SET v.numComments = v.numComments + 1 WHERE v.id = :bookId")
int updateCounter(#Param("bookId")Long bookId);
}
And now the question: What next? I think I can put the update of the Books entity annotating with #PostPersist a method of the entity Comments, but I have been unsuccessful so far. I can imagine something like this:
#PostPersist //This function in the entity Comments
protected void updateBooks() {
//Likely some call to the repository here that updates the count
// in books the info we have from current entity.
}
Any idea on how to do this? Some best practices about this kind of denormalization in JPA? Better to use the database triggers?
spring not managed your entity classes and your idea is possible but you must inject BooksRepository in enttiy class then stay at you get Nullpointerexception because spring not managed enttiy classes,The reason your BooksRepository not initlaized, try also read this post Bean injection inside a JPA #Entity and anotate entity class #Configurable after
try this
#PostPersist
protected void updateBooks(Comments comment) {
int totalComment = BooksRepository.updateCounter(comment.getBookId());
System.out.println(totalComment); // see totalComment in console
}
but good aprroach in service classes after call updateCounter when insert comment
example in your CommendService : when try a insert commend after call your updateCounter
if(comment.getBookId() != null) //Simple Control
{
CommentRepository.save(comment);
BooksRepository.updateCounter(comment.getBookId());
}

Spring Repository issue

I seem to be baffled on how JPA Repositories are suppose to work.
In a nut-shell
#Entity
public class User extends AbstractEntity {
protected final static String FK_NAME = "USER_ID";
#Column(nullable = false)
private String firstName;
#OneToMany(cascade = ALL, fetch = FetchType.LAZY, orphanRemoval = true)
#JoinColumn(name = "userId")
private List<Detail> details = new ArrayList<Detail>();
}
#Entity
public class Detail extends AbstractEntity {
Long userId;
String hello;
}
#Repository
public interface UserRepository extends CrudRepository<User, Long> {
User findByFirstName(#Param("firstName") String firstName);
}
And here is the only controller in the app:
#RestController
public class Home {
#Autowired
UserRepository userRepository;
#Autowired
DetailsRepository loanRepository;
#RequestMapping(value = "")
public HttpEntity home() {
User user = userRepository.findByFirstName("John");
if (user == null) {
user = new User();
user.setFirstName("John");
}
Detail detail = new Detail();
detail.setHello("Hello Msh");
user.getDetails().add(detail);
userRepository.save(user);
return new ResponseEntity("hi", HttpStatus.OK);
}
}
Below a screenshot from debugging session where the app just started and the get request to home() method creates new user, new detail, adds detail to user.
Below example - when the user is saved, the detail entity gets updated
Now on the next request, the old user John is found and has been added a new instance of detail.
The old user has been saved but now the newly created detail does not get updated outside.
How come this only works first time ?
Basically theres so much fail going on so that I would advise you to go a step backwards. If youre wana go the short path of getting a solution for exactly this problem continue reading ;)
First part related to the answer of Jaiwo99:
As I can see in the gradle view of intellij, your using Spring Boot. So it is necessary to place #EnableTransactionManagement on top of your configuration class. Otherwise the #Transacion annotation does not have any effect.
Second part your JPA/Hibernate model mapping. Theres so much bad practise on the net that it is no wonder that most beginners have troubles starting with it.
A correct version could look like (not tested)
#Entity
public class User extends AbstractEntity {
#Column(nullable = false)
private String firstName;
#OneToMany(cascade = ALL, fetch = FetchType.LAZY, orphanRemoval = true, mappedBy="user")
private List<Detail> details = new ArrayList<Detail>();
public void addDetail(Detail detail) {
details.add(detail);
detail.setUser(user);
}
}
#Entity
public class Detail extends AbstractEntity {
#ManyToOne
private User user;
private String hello;
public void setUser(User user){
this.user = user;
}
}
Some general advice related to creating a model mapping:
avoid bi-directional mappings whenever possible
cascade is a decision made on the service level and not at the model level and can have huge drawbacks. So for beginners avoid it.
I have no idea why people like to put JoinColumn, JoinTable and whatever join annotation on top of fields. The only reason to do this is when you have a legacy db (my opinion). When you do not like the names created by your jpa provider, provide a different naming strategy.
I would provide a custom name for the user class, because this is in some databases a reserved word.
Very simple, the first time you saved a new entity outside of hibernate session, the second time, the user object you got is a detached object, by default hibernate will not consider it is changed in this case.
*solution *
Move this logic to another service class, which annotated with #transactional
Or
Annotate your controller with transactional
Or
Override equals and hashCode method on user class may also help

spring security datamodel

I'm currently using the spring-security libraries and I asked myself the following question: How should I combine my database model with the spring-security tables?
As you know spring-security needs two tables (users and authorities) to define an authentication manager in the database. From my pov there are now two possibilities where I store my additional user-information (like email, lastname, last-logged-on, ....)
I could have a plain user-table for authentication purposes and another one for the rest (linked by the username)
I extend the user-table of spring-security with my necessary attributes.
What is the best design from your perspective? What are your experiences?
Lomu
I created a POJO User which represents the User entity as conceived by the Spring Security library, and secondly I created a POJO ProfiledUser to represent a specialized type of user of my application. It is called ProfiledUser because I needed a user associated to a profile. Of course, a similar approach can be applyied for every type of user you need to represent. Basically, if you need more than one type of user you can make your classes to extend the User POJO.
In the following you find the class, with the JPA annotations.
#Entity
#Table(name="USERS")
#Inheritance(strategy=InheritanceType.JOINED)
public class User implements UserDetails {
private static final long serialVersionUID = 1L;
private long id;
private String username;
private String password;
private boolean enabled = true;
Set<Authority> authorities = new HashSet<Authority>();
//...getters & setters
}
#Entity
#Table(name="PROFILED_USERS")
public class ProfiledUser extends User{
private static final long serialVersionUID = 1L;
//some custom attributes
private PersonalData personalData;
private ContactData contactData;
private AddressData addressData;
//...getters & setters
}
If you need to represent only one type of user, I think it should work to add attributes to the User class. However, I prefer to separate the abstract concept of user defined by the Spring Security framework from my business logic. So I'd recommend to implement your own SomethingUser and extend the User class.
A person is a person and you should have a class/table representing a person†.
A user is a user, and is different from a person (hence the two different words), and you should have a class/table representing a user.
Can a person exist without a user? Yes
Can a user exist without a person? No, a username belongs to someone.
#Entity
abstract class Party {
#Id
Long id;
String name;
#OneToMany
List<User> usernames = new ArrayList<>();
}
#Entity
class Individual extends Party {
DateTime dateOfBirth;
}
#Entity
class User {
#ManyToOne
Party party;
String username;
String password; //you better use BCrypt/Blowfish hashing!
Boolean enabled = true;
}
You could instead use a #OneToOne relationship if you only want one username per party.
† Actually you should have a more abstract class/table representing a legal party.

Resources