0

I set up two datasources in Spring Boot. In a controller I want to persist a couple of parent-child entities on both. They're almost identical bidirectional relationships, but the first one works, while the second emits a committing message but doesn't actually persist the child entity. Entities from working datasource

@Entity
public class Brand {

private Integer id;

private Set<Line> lines = new HashSet<Line>(); 

@Id
@GeneratedValue(strategy=GenerationType.AUTO)
@JsonIgnore
public Integer getId() {
    return id;
}

@JsonIgnore
@OneToMany(mappedBy = "brand", cascade = CascadeType.ALL, orphanRemoval = true)
public Set<Line> getLines() {
    return lines;
}

@Entity
public class Line {

private Integer id;

private Brand brand;

@Id
@GeneratedValue(strategy=GenerationType.AUTO)
public Integer getId() {
    return id;
}

@ManyToOne
@JoinColumn(name = "brand")
public Brand getBrand() {
    return brand;
}

while this ones are from second datasource, that persists the parent but not the child

@Entity
public class User {

private String id;

private List<UserCommit> userCommitList = new ArrayList<UserCommit>();

@Id
@GeneratedValue(strategy=GenerationType.AUTO)
public Integer getId() {
    return id;
}
}

@OneToMany(mappedBy = "user", cascade = CascadeType.ALL, orphanRemoval = true)
public List<UserCommit> getUserCommitList() {
    return userCommitList;
}

@Entity
public class UserCommit {

private Integer id;

private User user;

@Id
@GeneratedValue(strategy=GenerationType.AUTO)
public Integer getId() {
    return id;
}

@ManyToOne
@JoinColumn(name = "user")
public User getUser() {
    return user;
}

This is the the interesting part of the controller

    Brand brand = brandService.createFrom(fsDto.getBrand());
    Line line = lineService.createFrom(fsDto.getLine(), brand);
    AccessToken accToken = token.getAccount().getKeycloakSecurityContext().getToken();

    try {

        prodService.updateFrom(id, line, fsDto);

        User user = userService.createFrom(accToken);
        ucService.createFrom(user, id, "EDIT", "FIRST");

And these are the service methods

public Brand createFrom(String name) {

    Brand foundBrand = findByName(name);
    Brand brand = new Brand();
    if (foundBrand == null) {
        brand = brandRepo.save(brand);
        brand.setName(name);
    } else
        brand = foundBrand;

    return brand;

public Line createFrom(String name, Brand brand) {

    Line foundLine = findByNameAndBrand(name, brand);
    Line line = new Line();
    if (foundLine == null) {
        line.setName(name);
        line.setBrand(brand);
        line.getBrand().addLine(line);

    } else
        line = foundLine;

    return line;
}

public User createFrom(AccessToken accToken) {

    User user = findByKcId(accToken.getSubject());

    if(user == null) {
        user = new User();
        user.setKcId(accToken.getSubject());
        user.setName(accToken.getPreferredUsername());
        userRepo.save(user);
    }

    return user;

}

public UserCommit createFrom(User user, Integer prodId, String ucType, String stage) {

    UserCommit uc = new UserCommit();
    uc.setUser(user);
    uc.getUser().addUserCommit(uc);
    uc.setProdId(prodId);
    uc.setUCType(UserCommit.UCType.valueOf(ucType));
    uc.setStage(UserCommit.Stage.valueOf(stage));
    uc.setTime(LocalDateTime.now());

    return uc;
}
  • Brand gets persisted.
  • Line gets Persisted.
  • User gets persisted.
  • UserCommit DON'T get persisted.
  • Everything works if I explicitly call UserCommitService.save(), but I think it shouldn't be needed.

EDIT I updated my code to make the two couples of transactions even more similar, and now for the second one I got this classic error on lazy initialization.

org.hibernate.LazyInitializationException: failed to lazily initialize a collection of role: petmenu.entities.users.User.userCommitList, could not initialize proxy - no Session

What really drives me mad, is that Brand→Line works, while User→UserCommit doesn't. Even tracing JPA I cannot understand why User entity is not included in the session at UserCommit commiting.

2020-05-26 16:51:54.258 DEBUG 78269 --- [http-nio-192.168.1.10-8080-exec-2] o.s.orm.jpa.JpaTransactionManager        : Found thread-bound EntityManager [SessionImpl(1767213597<open>)] for JPA transaction
2020-05-26 16:51:54.258 DEBUG 78269 --- [http-nio-192.168.1.10-8080-exec-2] o.s.orm.jpa.JpaTransactionManager        : Creating new transaction with name [petmenu.services.users.UserService.createFrom]: PROPAGATION_REQUIRED,ISOLATION_DEFAULT
2020-05-26 16:51:54.259 DEBUG 78269 --- [http-nio-192.168.1.10-8080-exec-2] o.s.orm.jpa.JpaTransactionManager        : Exposing JPA transaction as JDBC [org.springframework.orm.jpa.vendor.HibernateJpaDialect$HibernateConnectionHandle@41ca5759]
2020-05-26 16:52:01.687 DEBUG 78269 --- [http-nio-192.168.1.10-8080-exec-2] o.s.orm.jpa.EntityManagerFactoryUtils    : Opening JPA EntityManager
2020-05-26 16:52:01.708 DEBUG 78269 --- [http-nio-192.168.1.10-8080-exec-2] org.hibernate.SQL                        : select user0_.id as id1_0_, user0_.kc_id as kc_id2_0_, user0_.name as name3_0_ from user user0_ where user0_.kc_id=?
2020-05-26 16:52:01.708 TRACE 78269 --- [http-nio-192.168.1.10-8080-exec-2] o.h.type.descriptor.sql.BasicBinder      : binding parameter [1] as [VARCHAR] - [80a3b4b1-00d1-4062-a7e5-1927b938c203]
2020-05-26 16:52:01.709 TRACE 78269 --- [http-nio-192.168.1.10-8080-exec-2] o.h.type.descriptor.sql.BasicExtractor   : extracted value ([id1_0_] : [INTEGER]) - [2005]
2020-05-26 16:52:01.709 TRACE 78269 --- [http-nio-192.168.1.10-8080-exec-2] o.h.type.descriptor.sql.BasicExtractor   : extracted value ([kc_id2_0_] : [VARCHAR]) - [80a3b4b1-00d1-4062-a7e5-1927b938c203]
2020-05-26 16:52:01.709 TRACE 78269 --- [http-nio-192.168.1.10-8080-exec-2] o.h.type.descriptor.sql.BasicExtractor   : extracted value ([name3_0_] : [VARCHAR]) - [user1]
2020-05-26 16:52:01.709 TRACE 78269 --- [http-nio-192.168.1.10-8080-exec-2] o.s.orm.jpa.JpaTransactionManager        : Triggering beforeCommit synchronization
2020-05-26 16:52:01.709 TRACE 78269 --- [http-nio-192.168.1.10-8080-exec-2] o.s.orm.jpa.JpaTransactionManager        : Triggering beforeCompletion synchronization
2020-05-26 16:52:01.709 DEBUG 78269 --- [http-nio-192.168.1.10-8080-exec-2] o.s.orm.jpa.JpaTransactionManager        : Initiating transaction commit
2020-05-26 16:52:01.709 DEBUG 78269 --- [http-nio-192.168.1.10-8080-exec-2] o.s.orm.jpa.JpaTransactionManager        : Committing JPA transaction on EntityManager [SessionImpl(1767213597<open>)]
2020-05-26 16:53:06.630 TRACE 78269 --- [http-nio-192.168.1.10-8080-exec-2] o.s.orm.jpa.JpaTransactionManager        : Triggering afterCommit synchronization
2020-05-26 16:53:06.671 TRACE 78269 --- [http-nio-192.168.1.10-8080-exec-2] o.s.orm.jpa.JpaTransactionManager        : Triggering afterCompletion synchronization
2020-05-26 16:53:06.671 DEBUG 78269 --- [http-nio-192.168.1.10-8080-exec-2] o.s.orm.jpa.JpaTransactionManager        : Not closing pre-bound JPA EntityManager after transaction
2020-05-26 16:53:06.671 DEBUG 78269 --- [http-nio-192.168.1.10-8080-exec-2] o.s.orm.jpa.JpaTransactionManager        : Found thread-bound EntityManager [SessionImpl(1767213597<open>)] for JPA transaction
2020-05-26 16:53:06.671 DEBUG 78269 --- [http-nio-192.168.1.10-8080-exec-2] o.s.orm.jpa.JpaTransactionManager        : Creating new transaction with name [petmenu.services.users.UserCommitService.createFrom]: PROPAGATION_REQUIRED,ISOLATION_DEFAULT
2020-05-26 16:53:06.671 DEBUG 78269 --- [http-nio-192.168.1.10-8080-exec-2] o.s.orm.jpa.JpaTransactionManager        : Exposing JPA transaction as JDBC [org.springframework.orm.jpa.vendor.HibernateJpaDialect$HibernateConnectionHandle@323b85aa]
2020-05-26 16:53:17.438 TRACE 78269 --- [http-nio-192.168.1.10-8080-exec-2] o.s.orm.jpa.JpaTransactionManager        : Triggering beforeCompletion synchronization
2020-05-26 16:53:17.472 DEBUG 78269 --- [http-nio-192.168.1.10-8080-exec-2] o.s.orm.jpa.JpaTransactionManager        : Initiating transaction rollback
2020-05-26 16:53:17.472 DEBUG 78269 --- [http-nio-192.168.1.10-8080-exec-2] o.s.orm.jpa.JpaTransactionManager        : Rolling back JPA transaction on EntityManager [SessionImpl(1767213597<open>)]
2020-05-26 16:53:17.472 TRACE 78269 --- [http-nio-192.168.1.10-8080-exec-2] o.s.orm.jpa.JpaTransactionManager        : Triggering afterCompletion synchronization
2020-05-26 16:53:17.472 DEBUG 78269 --- [http-nio-192.168.1.10-8080-exec-2] o.s.orm.jpa.JpaTransactionManager        : Not closing pre-bound JPA EntityManager after transaction
2020-05-26 16:53:17.473 DEBUG 78269 --- [http-nio-192.168.1.10-8080-exec-2] o.j.s.OpenEntityManagerInViewInterceptor : Closing JPA EntityManager in OpenEntityManagerInViewInterceptor
2020-05-26 16:53:17.496 ERROR 78269 --- [http-nio-192.168.1.10-8080-exec-2] o.a.c.c.C.[.[.[/].[dispatcherServlet]    : Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request processing failed; nested exception is org.hibernate.LazyInitializationException: failed to lazily initialize a collection of role: petmenu.entities.users.User.userCommitList, could not initialize proxy - no Session] with root cause 
4javier
  • 481
  • 2
  • 7
  • 22
  • Never worked with Entity but it looks like your saving the user than adding the commit. User createFrom(AccessToken accToken) -->saves the user UserCommit createFrom --> creates the usercommit Have you tried adding the commit then saving the user a second time just to see if that's the case? – Kevin Johnson May 23 '20 at 19:02
  • Yes. I do the same thing with Brand and Line and it works. – 4javier May 23 '20 at 19:15

1 Answers1

0

I studied a lot of stuff about transaction management etc. and I (hopefully) find the culprit. But every correction is thankfully accepted.

LSS

It seems that Spring Boot registers OpenEntityManagerInViewInterceptor just for the primary datasource (I find some info about this on the net, but nothing on official docs).

TL:DR

Bounding a session to the whole view and loading eagerly managed entities' collections, when running LineService.createFrom Brand's collection is already persisted, then the newly added Line gets flushed at the end of the transaction. On the counterpart without the view-session, I got no managed collection inside UserCommitService.createFrom transaction, then without an explicit call to UserCommitRepo.save the UserCommit will remain transient and will be silently discarded from flushing at transaction commit.

I don't know if I understood everything correctly, and if that's the case, I'd like somebody to explain what's the point in CascadeType.PERSIST (included in CascadeType.ALL) on a @OneToMany side of a bidirectional relationship.

4javier
  • 481
  • 2
  • 7
  • 22