spring-projects / spring-batch

Spring Batch is a framework for writing batch applications using Java and Spring
http://projects.spring.io/spring-batch/
Apache License 2.0
2.69k stars 2.33k forks source link

Fetching Entities described via @EmbeddedId in Spring Batch and with JpaCursorItemReader returns always single value #4441

Open dari220 opened 1 year ago

dari220 commented 1 year ago

Bug description Fetching Entities(UserRole in ManyToOne association) described via jakarta.persistence.EmbeddedId in Spring Batch and with JpaCursorItemReader returns always a single value.

Example: User has two UserRoles with roleId1=1 and roleId2=4. In ItemProcessor I get always either roleId1 or roleI2 but not both.

Environment Spring boot 3.1.3, Spring Batch 5.0.3, Hibernate-Core 6.2.7

Entities

@Entity
@Table( name = "user"
})
@Cache(usage = CacheConcurrencyStrategy.READ_WRITE)
public class User extends PersonExtendedImpl implements UserDetails, ToDto<UserDTO>, Serializable {

    @Id
    @GeneratedValue(strategy=GenerationType.IDENTITY)
    @Column(name = "User_ID")
    private Integer id;

    @Cache(usage = CacheConcurrencyStrategy.READ_WRITE)
    @OneToMany(
            mappedBy = "user",
            cascade = CascadeType.ALL,
            orphanRemoval = true)
    private List<UserRole> userRoles = new ArrayList<UserRole>();

        equals, hashcode, getter, setter

@Entity
@Table(name = "user_role",
       uniqueConstraints={@UniqueConstraint(columnNames = {"User_ID", "Role_ID"})}
)
@Cache(usage = CacheConcurrencyStrategy.READ_WRITE)
public class UserRole implements Serializable {

    @EmbeddedId
        private UserRoleKey id;

    @Setter(AccessLevel.NONE)
    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "User_ID", referencedColumnName = "User_ID", insertable=false, updatable=false)
    private User user;

    public UserRole(UserRoleKey id) {   
        this.id = id;
    }
        equals, hashcode, getter, setter
}

@Embeddable
public class UserRoleKey implements Serializable {

   @Column(name = "User_ID")
    private Integer userId;

   @Column(name = "Role_ID")
    private Integer roleId;

   equals, hashcode, getter, setter
}

Steps to reproduce

    @Bean
    @Scope("prototype")
    Step step( JobRepository jobRepository,
        JpaCursorItemReader<User> userPrivilegeReader,
        CustomerPrivilegeProcessor userPrivilegeProcessor,
        PlatformTransactionManager userTransactionManager) throws Exception {

        return new StepBuilder("customerPrivilegeStep", jobRepository)

                .<User, User> chunk(10, userTransactionManager)
                .reader( userPrivilegeReader )
                .processor( userPrivilegeProcessor )
                .writer(() -> System.out.println("NOT OKAY  "))
                .build();
    }

This Step produces correct sql statement but just one UserRole fetched in List of userRoles.

SQL:

            """
                SELECT u from User u 
                JOIN FETCH u.userRoles ur 
                WHERE u.expiresAt > CURRENT_TIMESTAMP 
                AND u.enabled = true 
                AND u.locked = false """
select u1_0.User_ID,u1_0.Auth_Type,u1_0.Birth_day,u1_0.Country_ID,u1_0.Email,u1_0.Enabled,u1_0.Expires_at,u1_0.First_Name,u1_0.Form_of_Address_ID,u1_0.Last_accessed_at,u1_0.Last_Name,u1_0.Last_updated_at,u1_0.Locked,u1_0.Locked_at,u1_0.Password,u1_0.Phone,u1_0.Registered_at,u1_0.Register_Type,u2_0.User_ID,u2_0.Role_ID,u1_0.Username 
from user u1_0 
join user_role u2_0 on u1_0.User_ID=u2_0.User_ID 
where u1_0.Expires_at>current_timestamp(6) 
and u1_0.Enabled=1 and u1_0.Locked=0

Important! I could not reproduce the same issue in Spring Boot 3.1.3 and Spring MVC application environment!!!

Expected behavior JpaItemReaders should fetch all associated Entities(UserRole's) into Collection of parent Entity(User).

dari220 commented 1 year ago

Strange, but reading same sql with JpaPagingItemReader works. Fetching gives expexted result.

fmbenhassine commented 11 months ago

Thank you for opening this issue. We do not exclude that this could be a bug in Spring Batch. However, we would like to validate that with a minimal complete verifiable example (the definition of UserDetails and PersonExtendedImpl are missing).

Could you please take some time to create a minimal example that reproduces the problem? To help you in reporting your issue, we have prepared a project template that you can use as a starting point. Please check the Issue Reporting Guidelines for more details about this.

Thank you for your collaboration.

dari220 commented 11 months ago

Thank for taking your time.

At the moment I can provide this ziped Maven Spring Project.

Model contains three entities. User(=parent) and two childs UserRole and Privilege. Please run the Project not in test mode but as it is. I'm displayig ItemReader results in console or log file.

Please,

  1. add your db dependency(default mariadb) in pom.xml and configure db in application,yml of your choice
  2. test data-ddl are src/main/resource/data.sql. Insert these data
  3. read output in log or console (found versus expected)

Sorry, but Im not experienced with inMemory database h2. It didn't worked for me at the moment. The issue with the JpaCursorItemReader appears when fetching multiple entities. Returns always 1 child entity. (Source file UserReader.java). This is the hql

              SELECT DISTINCT u from User u 
              LEFT JOIN FETCH u.userRoles ur 
              LEFT JOIN FETCH u.privileges pr 

I think, fetching multiple entities in one hql should work and makes sense when mutiplicity between entities is low. Darius Gurjazkas

Spring-batch-mcve.zip

KamalAhmad07 commented 10 months ago

hey @dari220 still your problem is pending so can i work on it

kjuyoung commented 3 months ago

Hi @dari220, When fetching the user entity based on the sql below, two child entities of the user are returned. But I don't know if this is the intended behavior for JpaCursorItemReader.

SELECT DISTINCT u from User u
LEFT JOIN u.userRoles ur
LEFT JOIN u.privileges pr

Hi @fmbenhassine, JpaPagingItemReader works fine in the case described by dari220, but JpaCursorItemReader does not work as desired in this case for JOIN FETCH. Is this the intended behavior for JpaCursorItemReader?