Hibernate wouldn't cascade entity saving - java

My program has 2 entities:
#Entity
public class User implements Serializable {
#ManyToMany(cascade={CascadeType.ALL},fetch = FetchType.EAGER,
targetEntity = Ip.class,mappedBy = "user")
#Cascade(org.hibernate.annotations.CascadeType.ALL)
#Fetch(value = FetchMode.SUBSELECT)
private Collection<Ip> allowedIp;
...
}
#Entity
public class Ip implements Serializable {
#Column(unique=false,updatable=true,insertable=true,nullable=true,
length=255,scale=0,precision=0)
#Id
private String value;
#ManyToMany(targetEntity = User.class)
private Collection<User> user;
...
}
I'm trying to persist a new User using a Spring JPA repository like the following:
#Transactional (readOnly = false)
#Repository
public interface UserRepository extends JpaRepository<User, String> {
}
List<Ip> allowedIp = new ArrayList<Ip>();
allowedIp.add(ipRepository.findOne("*"));
User user = new User();
user.setAllowedIp(allowedIp);
userRepository.save(user);
The problem is that Ip (the * value) is never persisted although if I added the JPA cascading annotation, and also the hibernate cascading annotation. Any idea why this problem is happening ?

You marked the #manytomany as mannaged by the IP class (mappedBy annotation). You have to add the User to the IP.user collection to have the reletionship persisted.
for (Ip ip : allowedIp) ip.getUser().add(user)

Related

Duplicate entry when using one to one relationship with shared primary key in JPA

I followed the example of Modeling With a Shared Primary Key as below:
#Entity
#Table(name = "users")
public class User {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "id")
private Long id;
//...
#OneToOne(mappedBy = "user", cascade = CascadeType.ALL)
#PrimaryKeyJoinColumn
private Address address;
//... getters and setters
}
#Entity
#Table(name = "address")
public class Address {
#Id
#Column(name = "user_id")
private Long id;
//...
#OneToOne
#MapsId
#JoinColumn(name = "user_id")
private User user;
//... getters and setters
}
However, if there are already a record with id 123456 in address table, then I tried to update the record like below:
Address po = new Address();
po.setId(123456L);
po.setCountry("TW");
AddressRepository.save(po);
Duplicate entry '123456' for key Exception will occur. Why JPA will insert a new record instead of merging it? How to solve this problem?
I know the reason finally. It is because the entity has version field and the version field in the new entity is null.
We need to dig into the source of of save() method in JPA.
#Transactional
public <S extends T> S save(S entity) {
if (entityInformation.isNew(entity)) {
em.persist(entity);
return entity;
} else {
return em.merge(entity);
}
}
Then, if we don't override the isNew(), it will use the default isNew() of JpaMetamodelEntityInformation.
#Override
public boolean isNew(T entity) {
if (!versionAttribute.isPresent()
|| versionAttribute.map(Attribute::getJavaType).map(Class::isPrimitive).orElse(false)) {
return super.isNew(entity);
}
BeanWrapper wrapper = new DirectFieldAccessFallbackBeanWrapper(entity);
return versionAttribute.map(it -> wrapper.getPropertyValue(it.getName()) == null).orElse(true);
}
Here, we can see that if version is present and the version is different from the existing record in the database, the entity will be a new entity and JPA will execute the insert action. Then, it will occur the error of duplicate entry.

Pass #Transactional method result from Service to Controller Layer Spring Boot

I'm trying to lazily fetch a ManyToMany relationship (Courses - Students) from the Service and pass the result to the Controller. While i'm in the Service, no LazyInitializationException is thrown, thanks to the #Transactional annotation. However, while i'm in the Controller the LazyInitializationException is thrown (while getting Course.students), because the Session was closed. How can i resolve this issue, without eagerly fetch the Collection? That's my code: Couse Model
#Entity
#Getter
#Setter
public class Course {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#Column
private String name;
#ManyToMany
#JoinTable(name = "COURSES_STUDENTS",
joinColumns = {#JoinColumn(name = "COURSE_ID")},
inverseJoinColumns = {#JoinColumn(name = "STUDENT_ID")})
private Set<Student> students;
public Course() {
this.students = new HashSet<>();
}
Student Model
#Entity
#Getter
#Setter
public class Student {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#Column
private String name;
#ManyToMany(mappedBy = "students")
private Set<Course> courses;
public Student() {
this.courses = new HashSet<>();
}
}
Course Repository
#Repository
public interface CourseRepository extends JpaRepository<Course, Long> {
}
Course Service
#Service
public class CourseService {
private final CourseRepository courseRepository;
#Autowired
public CourseService(CourseRepository courseRepository) {
this.courseRepository = courseRepository;
}
#Transactional
public ResponseEntity<List<Course>> findAll() {
return this.courseRepository.findAll().isEmpty() ? ResponseEntity.noContent().build()
: ResponseEntity.ok(this.courseRepository.findAll());
}
}
Course Controller
#Controller
#RequestMapping("/")
public class CourseController {
private final CourseService courseService;
#Autowired
public CourseController(CourseService courseService) {
this.courseService = courseService;
}
#GetMapping
public ResponseEntity<List<Course>> index() {
return this.courseService.findAll();
}
}
application.properties
spring.datasource.url=jdbc:h2:~/database;AUTO_SERVER=TRUE
spring.datasource.username=sa
spring.datasource.password=
spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.H2Dialect
spring.jpa.hibernate.ddl-auto=create-drop
spring.h2.console.enabled=true
spring.h2.console.path=/h2
spring.jpa.open-in-view=false
spring.mvc.hiddenmethod.filter.enabled=true
logging.level.org.springframework.web=DEBUG
spring.jpa.show-sql=true
spring.jpa.properties.hibernate.format_sql=true
Thanks in advance.
So there are 2 approaches :
What is this spring.jpa.open-in-view=true property in Spring Boot?
This is bad for performance and must be avoided at all costs.
use jpql queries to join fetch lazy collections needed in DAO layer so they are available in the controller when you need them to be.
All in all, do not use transactional to keep the db session open to fetch lazy collections. Just join fetch lazy collections in db / dao layer to have the data needed for each endpoint available.
If you want have a look here for how to use join fetch How to fetch FetchType.LAZY associations with JPA and Hibernate in a Spring Controller

I want to also save parent entity when save child entity by useing Spring data JPA

Here is the entity code:
#Entity
#Table(name = "t_user")
public class User{
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id;
//omit other fields
#OneToOne(cascade = javax.persistence.CascadeType.ALL, mappedBy = "user")
private Student student;
}
#Entity
#Table(name = "t_student")
public class Student{
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id;
#OneToOne
#JoinColumn(name = "c_user_id", referencedColumnName = "id", foreignKey = #ForeignKey(ConstraintMode.NO_CONSTRAINT), columnDefinition = "INT COMMENT 'user number'")
private User user;
}
Here is the save code:
#Service
#Transactional(rollbackFor = Exception.class)
public class UserServiceImpl implements UserService {
#Autowired
private final UserRepository userRepository;
#Autowired
private final PasswordEncoder pwdEncoder;
#Override
public Optional<UserBody> add(UserParam param) {
User user = User.from(param);
if(userRepository.countByName(user.getName()) > 0){
throw new BusinessException("user already exists");
}
user.setPassword(pwdEncoder.encode(param.getPassword()));
userRepository.save(user);
user.getStudent().setUser(user); // do this for update the column `c_user_id` of student table
return Optional.of(UserBody.from(user));
}
}
As above, when I try to save a user, It will be auto-save a student, but in the t_student table, the column c_user_id is empty. so I have to write the code user.getStudent().setUser(user) to update the column c_user_id.
so why? and what should I do to let the column c_user_id be auto-filled on saving user
ps: I don't want to change the mappedBy relation to Student, I know that could work, but I think a User can be a student, also can be another character, so if I add some other character, it will modify t_user table. This causes the table to become more and more bloated
The Student gets persisted. But you specified that the Student is responsible for maintaining the relationship between Student and User, and since Student.user is null this gets persisted in the database.
Therefore Hibernate is literally doing what you told it to do.

Hibernate and multiple OneToOne without creating new columns

I have the following situations with multiple OneToOne reletanships:
#Table(name = "User")
public class User {
#OneToOne(mappedBy = "settingColumnName")
private Settings setting;
}
#Table(name = "Account")
public class Account {
#OneToOne(mappedBy = "settingColumnName")
private Settings setting;
}
#Table(name = "Settings")
public class Settings{
#OneToOne()
#JoinColumn(name = "userColumnName")
private User user;
#OneToOne()
#JoinColumn(name = "accountColumnName")
private Account account;
}
Now, the issue here is that I have to create and save each model independently, because they are created as a result of StreamEvent capturing. Also, Hibernate will create automatically userColumnName and accountColumnName. What I would really need to do is to have something this:
Is this possible to implement with Hibernate? Could someone provide an example?
Do
#JoinColumn(name="userColumnName", insertable=false,updatable=false),
#JoinColumn(name="accountColumnName", insertable=false,updatable=false),
And Add two more fields in Settings Entity for these tow column and Map with same Column

Spring Data JPA / Hibernate - findByY(String y) method in #Repository where Y is accessible via object X

I've got a UserRepository:
#Repository
public interface UserRepository extends JpaRepository<User, Long>
{
}
where User:
#Entity
#Table(name = 'user')
public class User
{
#Id
private Long id;
#OneToOne(mappedBy = "owner", cascade = CascadeType.ALL, fetch = FetchType.LAZY)
private UserDetails userDetails;
}
and UserDetails:
#Entity
#Table(name = 'user_details')
public class UserDetails
{
#Id
private Long id;
#OneToOne(fetch = FetchType.LAZY)
#MapsId
private User owner;
private String name;
}
Packages, imports, getters and setters are excluded for cleaner code.
Now, how can I find users by their name? Adding this to the UserRepository interface will not work:
List<User> findByName(String name);
because it throws:
No property name found for type User
I am looking for something like this:
List<User> findByNameOfUserDetails(String name);
Please take a look at the Spring Data JPA docs here.
You'll need something like findByUserDetailsName(String name).
To resolve this ambiguity you can use _ inside your method name to manually define traversal points. So our method name would be findByUserDetails_Name(String name).

Categories

Resources