Hibernate 在创建表时交换列。它到底为什么要这样做?

Hibernate swaps columns around when creating a table. Why on earth does it do it?

提问人:Kodigas 提问时间:11/15/2023 更新时间:11/15/2023 访问量:48

问:

下面是一个 MRE:

package com.example.h2_demo;

import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest;
import org.springframework.test.context.ActiveProfiles;
import org.springframework.test.context.jdbc.Sql;

import java.util.List;

import static org.assertj.core.api.AssertionsForClassTypes.assertThat;

@DataJpaTest
@ActiveProfiles("test")
@Sql(
        executionPhase = Sql.ExecutionPhase.BEFORE_TEST_METHOD,
        scripts = {"/create_user_table.sql", "/insert_user.sql"}
)
public class H2DemoApplicationTest {
    private final UserRepository userRepository;

    @Autowired
    public H2DemoApplicationTest(UserRepository userRepository) {
        this.userRepository = userRepository;
    }

    @Test
    void testNothing() {
    }

    @Test
    void getUsers() {
        List<User> users = userRepository.findAll();
        assertThat(users).asList().hasSize(1);
        User actualUser = users.get(0);
        User expectedUser = expectedUser();
        assertThat(actualUser.getId()).isEqualTo(expectedUser.getId());
        assertThat(actualUser.getName()).isEqualTo(expectedUser.getName());
        assertThat(actualUser.getLastName()).isEqualTo(expectedUser.getLastName());
    }

    private User expectedUser() {
        User expectedUser = new User();
        expectedUser.setId(1L);
        expectedUser.setName("John");
        expectedUser.setLastName("Doe");
        return expectedUser;
    }
}
package com.example.h2_demo;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class H2DemoApplication {
    public static void main(String[] args) {
        SpringApplication.run(H2DemoApplication.class, args);
    }
}
package com.example.h2_demo;

import org.springframework.data.jpa.repository.JpaRepository;

public interface UserRepository extends JpaRepository<User, Long> {
}
package com.example.h2_demo;

import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import jakarta.persistence.Table;
import lombok.Getter;
import lombok.Setter;

@Entity
@Table(name = "users")
@Setter
@Getter
public class User {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    @Column(name = "name")
    private String name;
    @Column(name = "last_name")
    private String lastName;
}
CREATE TABLE IF NOT EXISTS users(
  id INTEGER PRIMARY KEY,
  name VARCHAR(50) NOT NULL,
  last_name VARCHAR(50)
);
INSERT INTO users
VALUES (1, 'John', 'Doe');
# application-test.properties

# show SQL executed by Hibernate
spring.jpa.properties.hibernate.show_sql=true

# show meta comments in Hibernate statements (as specified by org.springframework.data.jpa.repository.Meta annotations)
spring.jpa.properties.hibernate.use_sql_comments=true

# to enable line breaks and indentation in logged queries executed by Hibernate
spring.jpa.properties.hibernate.format_sql=true

logging.level.org.hibernate.type=trace

logging.level.org.springframework.jdbc.core=TRACE

# to see what @Sql scripts are executed
logging.level.org.springframework.test.context.jdbc=DEBUG

# to see what @Sql statements are executed
logging.level.org.springframework.jdbc.datasource.init=DEBUG

结果:

org.opentest4j.AssertionFailedError: 
expected: "John"
 but was: "Doe"
Expected :"John"
Actual   :"Doe"

从日志中:

Hibernate: 
    drop table if exists users cascade 
Hibernate: 
    create table users (
        id bigint generated by default as identity,
        last_name varchar(255),
        name varchar(255),
        primary key (id)
    )

看?Hibernate 打乱了列的顺序。当然,我可以禁用它(或在 DML 脚本中指定列),但我很好奇为什么 Hibernate 在如此简单的任务中丢球。从未见过这样的事情。Hibernate 是否保证表列的创建顺序与相应实体中的字段完全相同?如果不是,为什么不呢?这完全超出了我的理解ddl-auto

Java spring-boot 休眠 jpa

评论

0赞 Stanislav Bashkyrtsev 11/15/2023
1) 没有人使用 ddl-auto。您应该使用适当的数据库迁移工具,例如 Flyway。2) 不要使用 SQL 创建用户,而是使用您自己的存储库创建用户。这样,如果表发生更改,您就不必花时间维护脚本。此外,它还允许您为每个测试单独动态创建用户 - 该特定测试所需的用户,以及该测试所需的具体信息。3)当然,列的顺序很可能不能保证与字段的顺序匹配。如果是这样,我会感到惊讶。我怀疑 Java 是否保持了顺序 - 它们可能存储在哈希表中。
0赞 Kodigas 11/15/2023
@StanislavBashkyrtsev 是测试的常见做法(尽管禁用 ddl-auto 也可能是一种常见做法)@Sql
0赞 Stanislav Bashkyrtsev 11/15/2023
是的,这是一种常见的做法。但不是一个好的。在软件开发中,有很多常用的不良做法。试试这个: qala.io/blog/test-data-management.html
0赞 Lee Greiner 11/15/2023
一个简单的解决方案是在 SQL 语句中添加列名: INSERT INTO users (id, name, last_name) VALUES (1, 'John', 'Doe');这样做将消除您了解 Hibernate 如何创建它的 DDL 的需要,DDL 将来可能会发生变化。我同意@StanislavBashkyrtsev,最佳实践是使用测试用例以编程方式(或通过注释)创建数据。
0赞 Kodigas 11/16/2023
@StanislavBashkyrtsev顺便说一句,你到底是怎么想象的?现在,我只注入要测试的存储库。但是如果我需要执行一些插入,我还需要注入 、 、 、 (你明白了)......所以我变成了一个,因为如果其中任何一个失败,它可能会失败@BeforeEachAccountRepositoryCreditRepositoryCreditOrderRepositoryProductRepositoryCardRepositoryTestEveryRepositoryTest

答:

0赞 Kodigas 11/15/2023 #1

官方用户指南来看,这一点并不明显,但不,Hibernate 并不能保证这一点

如果 hibernate.hbm2ddl.auto 配置设置为 create,Hibernate 将生成以下数据库模式:

例 323.自动生成的数据库架构

create table Customer (
   id integer not null,
   accountsPayableXrefId binary,
   image blob,
   name varchar(255),
   primary key (id)
)

在指南的后面...

例 335.@LazyGroup示例

@Entity public class Customer {

  @Id     private Integer id;

  private String name;

  @Basic(fetch = FetchType.LAZY)  private UUID accountsPayableXrefId;

  @Lob    @Basic(fetch = FetchType.LAZY)  @LazyGroup("lobs")  private
Blob image;

  //Getters and setters are omitted for brevity

}

注意顺序。这种不匹配意味着 Hibernate 会按照它认为合适的方式创建表列

评论

0赞 Chris 11/15/2023
除非它或 JPA 规范规定在 DDL 生成中对列有顺序 - 否则没有任何保证。顺序不应该重要,也不应该被假定为受属性定位的控制,这就是为什么 JPA 提供程序显式列出他们正在选择、插入和更新的列的原因。