I'm new to Spring Data/Hibernate, and I'm trying to get my head around how you're supposed to handle concurrent users accessing data.
Suppose I've got a very simple domain model, consisting of houses and people who live in those houses:
House:
#Entity
public class House {
#Id
#GeneratedValue
private Long id;
#OneToMany(cascade = CascadeType.ALL)
private Set<Person> persons;
public void addPerson(Person p) {
persons.add(p);
public Set<Person> getPersons() {
return persons;
}
}
Person:
#Entity
public class Person {
#Id
#GeneratedValue
private Long id;
}
Currently I'm loading a House from a HouseRepository, and using this object to get/add persons.
That all works fine for a single user, but how are you supposed to support concurrent users? That is, say I've got a web application that has 2 concurrent users, who both want to view and add/edit persons from the same house.
What's the standard/best practice?
edit: to clarify what I'd like to do:
User 1 gets houseA from repository
User 2 gets houseA from repository
User 1 adds personA to houseA
User 2 gets persons from houseA, which contains personA
edit: Problem with #Transactional -
#SpringBootApplication
public class ExampleApplication implements CommandLineRunner {
#Autowired
private HouseRepository houseRepository;
public static void main(String[] args) {
SpringApplication.run(ExampleApplication.class, args);
}
#Override
public void run(String... strings) throws Exception {
House house = new House();
Person person = new Person();
person.setName("Bob");
house.addPerson(person);
houseRepository.save(house);
printPeople(house.getId());
}
#Transactional
public void printPeople(Long id) {
House house = houseRepository.findOne(id);
for (Person person : house.getPersons()) {
System.out.println(person.getName());
}
}
}
Throws org.hibernate.LazyInitializationException: failed to lazily initialize a collection of role: com.example.House.persons, could not initialize proxy - no Session at the foreach loop in printPeople
JPA is typically expected to be used within transactions. Use Spring's #Transactional AOP to mark your transaction boundaries, and it will handle concurrent access. Apply #Transactional to the highest-level method where it is relevant, which is typically the MVC controller method for a Web application.
Related
I’m trying to create linked list of nodes with CURRENT/PREVIOUS relation similar to the picture below.
I'm not sure if my solution is the right way to handle this scenario, but to achieve that I created two nodes with a single method to populate new messages as below:
#Builder
#Data
#NoArgsConstructor
public class Person {
#Id
#GeneratedValue
private Long id;
private String name;
#Relationship(type = "LATEST")
private Message message;
void newMessage(Message newMessage) {
newMessage.setPrevious(message);
message = newMessage;
}
}
#Builder
#Data
#NoArgsConstructor
public class Message {
#Id
#GeneratedValue
private Long id;
private String text;
#Relationship(type = "PREVIOUS")
private Message previous;
}
I also created a sample code to test this solution:
#SpringBootApplication
public class NewsFeedApplication {
public static void main(String[] args) {
SpringApplication.run(NewsFeedApplication.class, args);
}
#Bean
CommandLineRunner init(PersonRepository personRepository) {
return args -> {
Person personToAdd1 = Person.builder().name("John").build();
personToAdd1.newMessage(Message.builder().text("first message").build());
personToAdd1.newMessage(Message.builder().text("second message").build());
personToAdd1.newMessage(Message.builder().text("third message").build());
personRepository.save(personToAdd1);
personToAdd1.newMessage(Message.builder().text("New message.").build());
personRepository.save(personToAdd1);
};
}
}
I feel like I'm close, but I don't know how to reset the previous CURRENT relation and my solution produces output as:
So the question is:
If my approach is okay, how could I remove previous CURRENT relation.
If this approach is wrong, how could I implement linked list with CURRENT/PREVIOUS relations for nodes correctly.
I found the missing puzzle, i.e. detaching the supplier relation. I don't know why I assumed in advance that this relation should be deleted automatically by the spring data repository "save" method.
Working solution:
public interface PersonRepository extends Neo4jRepository<Supplier, Long> {
#Query("MATCH (n:Person {name: $name})-[r:LATEST]->() DELETE r")
void detachLatestFromPerson(String name);
}
void newMessage(PersonRepository personRepository, Message newMessage) {
personRepository.detachLatestFromPerson(name);
newMessage.setPrevious(message);
message = newMessage;
}
PS. I still have doubts, as I'm not sure if that's a good approach to handle this scenario, so if you know a better solution, go ahead and post it, we can always swap the 'best answer' :)
I'm writing some hql queries using the #Query annotation in a spring data jpa repository. I know that I can use the methods from the repository interface, but for learning purpose, I'm writing them explicitly.
Here is my Main class
#SpringBootApplication
public class Main implements CommandLineRunner {
#Autowired
PersonRepository personRepository;
public static void main( String[] args ) {
SpringApplication.run(Main.class, args);
}
/**
* if we delete the transactional annotation-> we get an exception
*/
#Override
#Transactional
public void run( String... args ) throws Exception {
saveOperation();
deleteOperationUsingHql();
}
private void saveOperation() {
Person p = new Person("jean", LocalDate.of(1977,12,12));
personRepository.save(p);
}
private void deleteOperationUsingHql() {
personRepository.deleteUsingHql(1L);
personRepository.flush();
Optional<Person> p = personRepository.findById(1L);
if (p.isPresent()){
System.out.println("still present");
}
}
}
My personRepository interface
public interface PersonRepository extends JpaRepository<Person, Long> {
#Query(value = "select p from Person p where p.id=?1")
List<Person> getById( Long id);
#Modifying
#Query(value = "delete from Person p where p.id=:id")
void deleteUsingHql( Long id );
}
The person class
#Entity
#Table(name = "Person")
public class Person {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
private LocalDate date;
// constructors,getters...omitted for brievety
}
everything is running well, but for the deleteOperationUsingHql(), even If I deleted the person from the database and even if I flush the modification to the database, the person with id=1 is still returned by the findById(1L) method. What should I do for making the findById(1L) returning an empty Optional.
My second question is about the #Transactional annotation, I know how it works in details, but I don't know why if we delete it, We get the following exception
Caused by: javax.persistence.TransactionRequiredException: Executing
an update/delete query
Could someone explains why I'm getting this exception when #Transactional is removed.
even If I deleted the person from the database and even if I flush the modification to the database, the person with id=1 is still returned by the findById(1L) method
That's normal, because you use a query to delete the person, instead of actually using the repository (and thus the EntityManager) delete method. Queries bypass the session cache completely, so Hibernate has no idea that this person has been deleted, and returns the instance in its cache. Solution: don't use a query. Alternate solution, clear the cache after deleting (for example by setting the clearAutomaticallyflag of the Modifying annotation to true).
Could someone explains why I'm getting this exception when #Transactional is removed.
Because when #Transactional is removed, there is no transaction being started by SPring before executing the method, and as you can see from the error message, delete queries must be executed inside a transaction.
I am currently working on a medium sized Java project with Hibernate and I have come across what seems to be a rare but quite persistent error. The situation is as follows: I have a Student entity who has a bidirectional many-to-many relation to an Education entity (implemented as a join table on the database) and an Admin entity who is a subclass of Student. My code allows for a Student to be "upgraded" to an Admin by removing the Student from the database, creating a new Admin based on the Student and persisting this Admin. However, whenever this happens, Hibernate throws the following error on EntityManager.flush():
org.hibernate.HibernateException: Found two representations of same collection: domain.Student.enrolledEducations
Below you can find the relevant code:
Education class
#Entity
public class Education {
...
#ManyToMany
#JoinColumn(name = "education_id")
private Set<Course> courses = new HashSet<>();
Student class
#Entity
public class Student {
....
#ManyToMany
#JoinColumn(name = "student_id")
private Set<Education> enrolledEducations = new HashSet<>();
Admin class
#Entity
public class Admin extends Student {
...
public Admin(Student student) {
this.setId(student.getId());
this.setFirstName(student.getFirstName());
this.setLastName(student.getLastName());
this.setEmail(student.getEmail());
this.setSalt(student.getSalt());
this.setSuperAdmin(false);
this.setEnrolledEducations(student.getEnrolledEducations());
this.setSessions(student.getSessions());
this.setManagedEducations(new HashSet<Education>());
}
Database methods
public Admin upgrade(Person person) {
Admin admin;
if (person instanceof Student){
removeStudent((Student) person);
admin = new Admin((Student) person);
}
else{
removePerson(person);
admin = new Admin(person);
}
addAdmin(admin); //exception happens here
return admin;
}
public void addAdmin(Admin admin) {
manager.getTransaction().begin();
if(manager.contains(admin)){
manager.merge(admin);
}
else{
manager.persist(admin);
}
manager.flush(); //exception happens here
manager.getTransaction().commit();
}
Test method
#Test
public void getEducationsForAdmin_and_upgrade_to_admin_work_correctly(){
educationSetup();
Admin admin1 = facade.upgrade(student1); //exception happens here
Admin admin2 = facade.upgrade(student2);
admin1.addNewEducation(education1);
admin1.addNewEducation(education2);
admin2.addNewEducation(education1);
facade.updateAdmin(admin1);
facade.updateAdmin(admin2);
Set<Education> educations1 = new HashSet<>(facade.getEducationsForStudent(admin1));
Set<Education> educations2 = new HashSet<>(facade.getEducationsForStudent(admin2));
assertTrue("admin 1 does not have exactly 1 education", educations1.size()==1);
assertTrue("admin 2 does not have exactly 2 educations", educations2.size()==2);
assertTrue("admin 1 does not have the IT education",educations1.contains(education1));
assertTrue("admin 2 does not have the IT education",educations2.contains(education1));
assertTrue("admin 2 does not have the M education",educations2.contains(education2));
}
It seems that you have a problem that both Admin and Student have the same identifier.
Since the Admin is created by calling the new function, it is not in the persistent state, the code
manager.contains(admin)
will always return false, so it will always go to the manager.persist statement.
Since Admin is a different object with the same identifier, you will get the exception
Found two representations of same collection
All you need to do is to add
manager.delete(person)
in your
removePerson
function. It should solve this problem.
I have a simple entity called Game. I want to allow my users to edit multiple of these entities at once. Therefore I need a form that contains multiple Game Entities.
The problem: When the form is submitted and I invoke hasErrors() my custom ad-hoc validate method in the Game entities is never called. Only the validations marked by annotations are checked and produce errors when they are invalid.
This is the Game Entity:
#Entity
public class Game extends Model {
#Id
public Long id;
#ManyToOne
#Constraints.Required
public Team team1;
#ManyToOne
#Constraints.Required
public Team team2;
//the validate method does not get called
public String validate()
{
System.out.println("Validating the Game Entity.");
if(team1.id == team2.id)
return "You have to choose two different teams!";
return null;
}
public static Model.Finder<Long,Game> find = new Model.Finder<Long,Game>(Long.class, Game.class);
}
This is the Form that contains multiple Game Entities.
public class GameForm {
#Valid
public List<Game> games;
public GameForm()
{
games = new ArrayList<Game>();
}
}
This is the controller method.
public static Result save()
{
Form<GameForm> gameForm = form(GameForm.class).bindFromRequest();
if(gameForm.hasErrors())
return badRequest(create.render(gameForm));
return redirect(
routes.Games.index()
);
}
The docs say that ad-hoc validation only works on the "top" object.
http://www.playframework.com/documentation/2.2.x/JavaForms
For your Form this is probably List, not Game.
I hava a basic Hibernate/JPA question. I want to find a best practice solution for saving entities. I have a List of Entities and many of them might be altered so I want to save them all at once.
I believe everything is pretty much standard. (Just example code for readability reasons)
Entity: Car
#Entity
public class Car implements Serializable {
private static final long serialVersionUID = 1L;
#Id
private id;
private String model;
// .... Setter
// .... Getter
}
Service Class: CarService
#Named
#Transactional
public class CarServiceImpl implements CarService {
#PersistenceContext
private EntityManager entityManager;
#Override
public List<Car> findAll() {
TypedQuery<Car> q = entityManager.createQuery(
"FROM Car", Car.class);
return q.getResultList();
}
#Override
public void saveEntity (Car car) {
/* What exactly am I doing here? */
}
}
Controller: CarEditController
#Named
public class CarEditController implements Serializable {
private static final long serialVersionUID = 1L;
#Inject
private CarService carService;
private List<Car> cars;
public List<Car> getCars () {
return carService.findAll();
}
public void setCars (List<Car> cars) {
this.cars = cars;
}
public void btn_newClick() {
Car newCar = new Car();
car setModel("BMW");
cars.add(newCar);
}
public void btn_saveClick() {
for (Car car : cars) {
carService.saveEntity(car);
}
}
}
I found quite a few ways of saving the entity. The obvious are entityManager.merge(car) for existing entities and entityManager.persist(car) for new ones. In theory thats easy but how do I know which entity is new?
The documentation suggests entityManager.flush(), which in theory updates all existing and inserts all new entities. It works but only if I fetch the entity with find().
The Question:
I want to fetch all entities in one step, add new ones and then save them all in one methode (btn_saveClick()). How is this task best accomplished?
Just check if the #Id is been set.
#Override
public void saveEntity (Car car) {
if (car.getId() == null) {
entityManager.persist(car);
} else {
entityManager.merge(car);
}
}
More common approach, however, is to offer separate create() and update() service methods.
I'm not familiar with JPA but in hibernate there is session.saveOrUpdate()
for(Car car : existingAndNewCars)
{
session.saveOrUpdate(car);
}
Update:
As i understand JPA, its merge is like session.merge which is totally different as it doesn't track changes to object supplied to it while persist/save/update/saveOrUpdate would track subsequent changes to car, leading to subtle differences
Update:
since you use the same entitymanager it should suffice to
#Override
public void saveEntity (Car car) {
if (car.getId() == null) {
entityManager.persist(car);
}
without the subtle difference of persist and merge
The flush operation will operate on all entities in the current Hibernate session - new entities are inserted and existing entities are updated if they have changed.
You need to ensure that all entities are attached to the session. You do this by using merge as you correctly say. After you have loaded all of the entities the session is closed when the transaction ends. Your objects are then in a detached state i.e. have been persisted but are no longer attached to a session.
I would amend your logic so that your carService#save takes a List. It can then call merge on each one (attaching them to the session). Then when your transaction ends Hibernate will flush all changes to the database at once.