Spring Data Jpa 验证在使用Long时有效,但在使用UUID时无效

0pizxfdo  于 2023-05-07  发布在  Spring
关注(0)|答案(2)|浏览(263)

我使用Java和Spring Data JPA。到目前为止,我一直使用Long作为我的ID。但事实证明,我需要它在我的DTO。因此,出于安全原因,我需要UUID或GUID。所以我读了一点关于它的信息,并从Long更改为UUID。期望它是顺利的,没有太多的问题切换,但我们在这里。
所以我有一个Entity,Repository,DTO,Mapper(由mapstruct生成)。我列出所有这些的原因是,如果你对我的代码结构有建议的话。
我有一个测试,那就是测试存储库。
这就是考验

//I'm using this on top of my test class
@DataJpaTest
public class RoleRepositoryTests {
@Test
    public void saveToDBShouldThrowExceptionTest(){
        //Given
        Role userRole = new Role();

        try {
            //When
            roleRepository.save(userRole);

            //Then
            fail("Expected ConstraintViolationException was not thrown");
        } catch (ConstraintViolationException e) {
            //The above will throw two exceptions cuz
            //both @NotBlank and @NotNull will throw an exception.
            //it throws them in random order every time
            assertThat(e.getConstraintViolations().size()).isEqualTo(2);
            return;
        }
        fail("Different Exception was thrown");
    }
}

实体

@Entity
@Getter @Setter
@ToString
@NoArgsConstructor
@Table(name = "Roles")
public class Role {

    @Id @GeneratedValue(strategy = GenerationType.UUID)
    private UUID id;

    @NotNull
    @NotBlank
    private String name;

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        Role role = (Role) o;
        return id.equals(role.id) && name.equals(role.name);
    }

    @Override
    public int hashCode() {
        return Objects.hash(id, name);
    }
}

仓库

@Repository
public interface RoleRepository extends CrudRepository<Role, UUID> {
    Optional<Role> findByName(String name);
}

在测试中,我期望roleRepository.save(userRole)抛出ConstraintViolationException。因为某些原因它不这样做。自从我从Long切换到UUID后,我决定回去把东西改回Long,看看是否有效(当然,我改变了存储库、生成类型和字段类型)。令人困惑的是它起作用了。测试抛出了异常,因此验证工作正常。我不知道是什么引起的。
@DataJpaTest默认在内存db中创建一个h2。我没有改变这一点,这就是我正在测试的。表、约束和非空值将按预期创建。

create table Roles (
       id uuid not null,
        name varchar(255) not null,
        primary key (id)
    )

它设法以某种方式将一个为null的名称保存到上面的表中,尽管not null在那里。
我试着对我的真实的数据库(ms sql)Roles Table进行测试。没有抛出异常,但也没有将任何内容保存到数据库。使用这个注解@AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE)来测试真实的数据库。
它每次都会生成不同的UUID。(Idk如果它应该这样做或不)

我所拥有的所有实体都会发生这种情况所以我不知道我错过了什么。

我试过用maven做清洁建筑。我已经尝试将@Validated添加到我的存储库和实体中。我已经尝试重新启动我的IDE(IntelliJ)我会尝试重新启动我的电脑后,我张贴这一点。没成功尝试了更多的东西,得到了一个 unrecognized id type:uuid -〉java.util.UUID。通过执行@Id @GeneratedValue(strategy = GenerationType.IDENTITY) private UUID id;获得
找到了这个post。正在阅读它。

af7jpaap

af7jpaap1#

找到解决办法了!不允许数据库生成GUID/UUID。你在代码中生成它。

@Entity
@Getter @Setter
@ToString
@Table(name = "Roles")
public class Role {

    //Make this String and remove the @GeneratedValue annotation
    @Id
    private String id;

    @NotNull
    @NotBlank
    private String name;

    //If an empty entity is created generate a UUID. (You can still overwrite it later)
    public Role() {
        this.id = UUID.randomUUID().toString();
    }

    public Role(String id, String name) {
        this.id = id;
        this.name = name;
    }

}

另外,不要忘记将存储库更新为String!
而且它仍然不会直接抛出ConstraintViolationException。
这是有效的,但是当你试图保存违反验证的东西时,它会抛出一个org.springframework.transaction.TransactionSystemException:无法提交由导致的JPA事务导致原因:jakarta.validation.ConstraintViolationException:类验证失败。我希望它直接抛出ConstraintViolationException,但我们在这里。
仍然没有使用h2进行验证。所以基本上我什么都没解决。
旧半溶液:
有点找到了解决方案,但这并不是我所期望的。
所以导致这个问题的原因是db(h2或mssql)和spring数据jpa(hibernate)不能就该怎么做达成一致。我最接近的解决办法是

@Id
@GenericGenerator(name = "uuid", strategy = "org.hibernate.id.UUIDGenerator")
@GeneratedValue(generator = "uuid")
private UUID id;

@Id
@GeneratedValue(strategy = GenerationType.UUID)
private UUID id;

使用Hibernate 6+

@Id
@UuidGenerator(style = UuidGenerator.Style.RANDOM)
//you can add this but it will just be ignored    
//@GeneratedValue(strategy = GenerationType.IDENTITY)
private UUID id;

这是可行的,但是当你试图保存一些违反验证的东西时,它会抛出一个org.springframework.transaction.TransactionSystemException: Could not commit JPA transaction,这是由Caused by: jakarta.validation.ConstraintViolationException: Validation failed for classes引起的。我希望它直接抛出ConstraintViolationException,但我们在这里。
这也不适用于H2。由于某种原因,h2不尊重验证。它直接忽略了表和实体验证。
解决方案,应该已经工作了开箱即用,但它不会与mssql也不是h2是

@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private UUID id;

H2很好地(虽然抛出了一些异常,但没有创建表)告诉我它不支持它。
MSSQL告诉我它不知道uuid是什么。org.springframework.orm.jpa.JpaSystemException: unrecognized id type : uuid -> java.util.UUID。尝试将GenericGenerator策略也更改为guid。从这里得到的想法。(guid -在MS SQL Server和MySQL上使用数据库生成的GUID字符串。)
如果你设法让它与.IDENTITY一起工作,我认为你需要这个(newsequentialid())(newId())作为mssql的默认值。
我相信有一个更好的解决方案,所以我不会把这个标记为答案。更好的解决方案就在^上面。

pbossiut

pbossiut2#

您还可以指定UUID对象在数据库中Map到的类型。也许这可以帮助你与两个数据库。
在你的应用程序.yml文件中添加例如:
spring.jpa.properties.hibernate.type.preferred_uuid_jdbc_type: CHAR
这将把UUIDMap到MSSQL中的CHAR列。您甚至可以通过spring配置文件切换应用程序上下文来更改测试上下文的Map。
参见:https://docs.jboss.org/hibernate/orm/6.2/migration-guide/migration-guide.html

相关问题