Spring Data JPA / Hibernate 生成单个 id 用于在单个事务中持久化的所有实体

huangapple go评论67阅读模式
英文:

Spring Data JPA / Hibernate generating single id for all the entities persisted within single transaction

问题

我有一个遗留应用程序,我试图将其迁移到更新的Apache Camel Spring Boot应用程序,我在其中使用Spring Data JPA和Hibernate作为ORM框架。Sybase是应用程序使用的DB。

我有许多JPA实体可用,其中的id被注释为:

@Id
@Column(name = "entity_pk")
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long entity_pk;

我有一个方法,我试图创建多个实体,然后逐个将所有实体持久化到DB中,使用JpaRepository save()方法。所有这些都发生在单个事务中。

我的问题是,Hibernate只生成一个唯一的id,然后在持久化所有实体时使用它。

我正在寻找的是一种告诉Hibernate的方法,检查每个表的主键,即entity_key列的最新值,然后生成下一个值。类似于每一行的自动递增值。列在数据库中创建为IDENTITY列。

我不想使用任何自定义id生成器来实现这一点。有没有办法只使用注解和/或配置来实现这一点?

编辑1: <br/>
只是想介绍现有遗留应用程序的背景。该应用程序使用Hibernate 3.x,并使用<generator class="native">,显然可以按预期工作。我从现有的DB值中确认了这一点。

我使用的是Hibernate 5.x,与Spring Boot 2.x和Spring 5.x一起使用。

我尝试使用GenerationType.AUTO以及hibernate.id.new_generator_mappings=false,只是为了确保根据此处给出的说明触发LegacyFallbackInterpreter,如下所示:<br/>
https://stackoverflow.com/questions/1212535/the-equivalent-of-generator-class-native-generator-using-mysql-and-hiberna
<br/><br/>
但什么都不起作用。

编辑2:<br/>
添加一些示例代码。这不是确切的代码,但类似于我试图在我的应用程序中实现的内容。<br/>
实体类:

@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
@ToString
@Entity
@Table(name = "block")
public class Block {
...
}

@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
@ToString
@Entity
@Table(name = "allocation")
public class Allocation {
...
}

@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
@ToString
@Entity
@Table(name = "booking")
public class Booking {
...
}

<br/><br/>
仓库:

@Repository
public interface BlockRepository extends JpaRepository<Block, Long> {
}

@Repository
public interface AllocationRepository extends JpaRepository<Allocation, Long> {
}

@Repository
public interface BookingRepository extends JpaRepository<Booking, Long> {
}

@Component
public class DbStore {
...
}

<br/><br/>
服务类:

@Service
public class AllocationHandler {
...
}

<br/><br/>
驱动程序类:

@Component
public class Transformer {
...
}

<br/><br/>
主类:

@SpringBootApplication
@EnableJpaRepositories(basePackages = "com.example.demo.repository")
public class DemoApplication implements ApplicationRunner {
...
}

<br/><br/>
我也在Github上添加了这段代码:<br/> https://github.com/viveksinghr89/spring-data-jpa-poc
<br/><br/>
我无法完全复制它,但部分功能确实像实际项目一样运行,其中 BlockAllocation 使用相同的id,而不是根据其自身identity列的最新值使用不同的id。
<br/><br/>
日志:

2023-04-17T23:41:05.674+05:30  INFO 21052 --- [           main] com.example.demo.DemoApplication         : Started DemoApplication in 6.195 seconds (process running for 6.718)
2023-04-17T23:41:05.678+05:30  INFO 21052 --- [           main] com.example.demo.AllocationHandler       : Persist block in DB
...

<br/><br/>
H2 DB 屏幕截图:
Spring Data JPA / Hibernate 生成单个 id 用于在单个事务中持久化的所有实体

<br/><br/>
请告诉我如果你能找出解决办法。将非常有帮助。谢谢。

英文:

I have a legacy application which I am trying to migrate to newer Apache Camel Spring Boot application where I am using Spring Data JPA and Hibernate as an ORM framework. Sybase is the DB used by application.

I have many JPA entities available where ids are annotated with:

@Id
@Column(name = &quot;entity_pk&quot;)
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long entity_pk;

I have a single method where I am trying to create multiple entities and then persisting all the entities one by one into the DB using JpaRepository save() method. All this is happening within a single transaction

My issue here is, hibernate is generating only one unique id and using it while persisting all the entities.

What I am looking for is a way to tell hibernate to check the latest value in the primary key i.e. entity_key column of each table and then generate the next value. Sort of auto incremented value for each row. Columns are created as IDENTITY column in the database.

I do not want to use any custom id generator for this. Is there any way to achieve this just by using annotations and/or configurations?

EDIT 1: <br/>
Just wanted to give background on existing legacy application. This application uses Hibernate 3.x and uses &lt;generator class=&quot;native&quot;&gt; which apparently works as expected. I confirmed that from existing DB values.

I am using Hibernate 5.x along with Spring boot 2.x and Spring 5.x.

I have tried using GenerationType.AUTO along with hibernate.id.new_generator_mappings=false just to make sure the LegacyFallbackInterpreter kicks in as per the instructions given here: <br/>
https://stackoverflow.com/questions/1212535/the-equivalent-of-generator-class-native-generator-using-mysql-and-hiberna
<br/><br/>
But nothing works.

Edit 2:<br/>
Adding some sample code. It is not the exact code but similar to what I am trying to achieve in my application.<br/>
Entity classes:

@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
@ToString
@Entity
@Table(name = &quot;block&quot;)
public class Block {

    @Id
    @Column(name = &quot;block_pk&quot;)
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long blockPk;

    @Version
    @Column(name = &quot;version&quot;)
    private int version;

    @OneToMany(mappedBy = &quot;block&quot;, cascade = CascadeType.ALL, orphanRemoval = true)
    @OrderBy(&quot;allocationPk&quot;)
    @Fetch(FetchMode.SELECT)
    @ToString.Exclude
    private Set&lt;Allocation&gt; allocations;

    @Column(name = &quot;quantity&quot;)
    private BigDecimal quantity;

    public void addAllocation(Allocation allocation) {
        if (allocations == null) {
            allocations = new HashSet&lt;&gt;();
        }

        allocations.add(allocation);
    }
}

@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
@ToString
@Entity
@Table(name = &quot;allocation&quot;)
public class Allocation {

    @Id
    @Column(name = &quot;allocation_pk&quot;)
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long allocationPk;

    @Version
    @Column(name = &quot;version&quot;)
    private int version;

    @ManyToOne
    @JoinColumn(name = &quot;block_pk&quot;, nullable = false)
    @Fetch(FetchMode.SELECT)
    @ToString.Exclude
    private Block block;

    @Column(name = &quot;quantity&quot;)
    private BigDecimal quantity;
}

@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
@ToString
@Entity
@Table(name = &quot;booking&quot;)
public class Booking {
    @Id
    @Column(name = &quot;booking_pk&quot;)
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long bookingPk;

    @Version
    @Column(name = &quot;version&quot;)
    private int version;

    @Column(name = &quot;block_Id&quot;)
    private Long blockId;
}

<br/><br/>
Repositories:

@Repository
public interface BlockRepository extends JpaRepository&lt;Block, Long&gt; {
}

@Repository
public interface AllocationRepository extends JpaRepository&lt;Allocation, Long&gt; {
}

@Repository
public interface BookingRepository extends JpaRepository&lt;Booking, Long&gt; {
}

@Component
public class DbStore {
    @Autowired
    private BlockRepository blockRepository;
    @Autowired
    private AllocationRepository allocationRepository;
    @Autowired
    private BookingRepository bookingRepository;

    public Block save(Block block) {
        return blockRepository.save(block);
    }

    public Allocation save(Allocation allocation) {
        return allocationRepository.save(allocation);
    }

    public Booking save(Booking booking) {
        return bookingRepository.save(booking);
    }
}

<br/><br/>
Service Class:

@Service
public class AllocationHandler {
    private static final Logger LOGGER = LoggerFactory.getLogger(AllocationHandler.class);
    @Autowired
    private DbStore dbStore;

    public List&lt;Allocation&gt; handleRequest(String input) {
        Block block = new Block();
        block.setQuantity(new BigDecimal(1000000));

        // Persist Block
        LOGGER.info(&quot;Persist block in DB&quot;);
        block = dbStore.save(block);
        LOGGER.info(&quot;Persisted Block in DB with Id [{}]&quot;, block.getBlockPk());

        Allocation allocation = new Allocation();
        allocation.setQuantity(new BigDecimal(500000));

        block.addAllocation(allocation);
        allocation.setBlock(block);

        LOGGER.info(&quot;Persist allocation in DB&quot;);
        allocation = dbStore.save(allocation);
        LOGGER.info(&quot;Persisted allocation in DB with Id [{}]&quot;, allocation.getAllocationPk());

        LOGGER.info(&quot;Update Block&quot;);
        block = dbStore.save(block);

        Booking booking = new Booking();
        booking.setBlockId(block.getBlockPk());

        LOGGER.info(&quot;Persist booking in DB&quot;);
        booking = dbStore.save(booking);
        LOGGER.info(&quot;Persisted Booking in DB with Id [{}]&quot;, booking.getBookingPk());

        return Arrays.asList(allocation);
    }
}

<br/><br/>
Driver Class:

@Component
public class Transformer {
    @Autowired
    private AllocationHandler allocationHandler;

    public void transform(String input) {
        allocationHandler.handleRequest(input);
    }
}

<br/><br/>
Main:

@SpringBootApplication
@EnableJpaRepositories(basePackages = &quot;com.example.demo.repository&quot;)
public class DemoApplication implements ApplicationRunner {
	@Autowired
	private Transformer transformer;

	public static void main(String[] args) throws InterruptedException {
		SpringApplication.run(DemoApplication.class, args);
	}

	@Override
	public void run(ApplicationArguments args) throws Exception {
		transformer.transform(&quot;Test&quot;);
	}
}

<br/><br/>
I have also added this code in Github:<br/> https://github.com/viveksinghr89/spring-data-jpa-poc
<br/><br/>
I am unable to replicate it completely but yes partially it is working like the actual project wherein Block and Allocation are using the same id instead of using different ids as per the latest value in their identity column.
<br/><br/>
Logs:

2023-04-17T23:41:05.674+05:30  INFO 21052 --- [           main] com.example.demo.DemoApplication         : Started DemoApplication in 6.195 seconds (process running for 6.718)
2023-04-17T23:41:05.678+05:30  INFO 21052 --- [           main] com.example.demo.AllocationHandler       : Persist block in DB
2023-04-17T23:41:05.755+05:30  INFO 21052 --- [           main] com.example.demo.AllocationHandler       : Persisted Block in DB with Id [97]
2023-04-17T23:41:05.755+05:30  INFO 21052 --- [           main] com.example.demo.AllocationHandler       : Persist allocation in DB
2023-04-17T23:41:05.761+05:30  INFO 21052 --- [           main] com.example.demo.AllocationHandler       : Persisted allocation in DB with Id [97]
2023-04-17T23:41:05.761+05:30  INFO 21052 --- [           main] com.example.demo.AllocationHandler       : Update Block
2023-04-17T23:41:05.823+05:30  INFO 21052 --- [           main] com.example.demo.AllocationHandler       : Persist booking in DB
2023-04-17T23:41:05.825+05:30  INFO 21052 --- [           main] com.example.demo.AllocationHandler       : Persisted Booking in DB with Id [65]

<br/><br/>
H2 DB Screenshot:
Spring Data JPA / Hibernate 生成单个 id 用于在单个事务中持久化的所有实体

<br/><br/>
Please let me know if you are able to figure something out. Would be really helpful. Thank you.

答案1

得分: 1

我已经测试过与H2文件数据库相似的配置。显然,不能保证ID会连续生成而没有间隙。唯一的保证是它们将是唯一的。在我的情况下,在应用程序首次运行时,空数据库的ID从1开始,并以1递增。但在重新启动后(不删除表),ID继续从33、34等开始。

英文:

I have tested similar configuration with H2 file database. Apparently there is no guarantee that the id will be generated without gaps. The only guarantee is that they will be unique.

In my case after the first run of the application with an empty database the ids started with 1 and were incremented by 1. But after restart (without dropping the tables) the id continued with 33, 34 etc.

huangapple
  • 本文由 发表于 2023年4月17日 19:26:07
  • 转载请务必保留本文链接:https://go.coder-hub.com/76034654.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定