插入新实体:Spring Data JPA与Hibernate的EntityManager对比

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

Insert new entity: Spring Data JPA vs. Hibernate's EntityManager

问题

请查看下面的两个代码示例,我将在我的Spring Boot项目中使用它们。它们都只是做同样的事情 - 将一个新对象添加到users表中,该表由具有@Id定义为用户名的User实体表示,并在email列上施加了unique约束(还有其他一些列,但出于简洁起见,这里没有显示)。注意:我不能简单地使用CrudRepositorysave()方法,因为如果现有记录和新对象都具有相同的username值,它会将它们合并起来。相反,我需要插入一个新对象,并为重复数据持久性引发适当的异常。

我的问题是哪个选项应该优先考虑。通过EntityManager,我不需要构造SQL语句。除此之外,有没有一种方法可能比另一种方法提供更多优势(尤其是在性能和资源消耗方面)?

此外,当我阅读关于在Spring Boot中进行数据持久化的最新书籍和教程时,它们主要集中在Spring Data JPA上。例如,《Spring in Action》的第5版几乎没有涉及Hibernate的EntityManager。这是否意味着直接处理Hibernate在现代项目中可能被视为一种“老派”做法,通常应该避免?

选项#1:Hibernate的EntityManager

@RestController
@RequestMapping(path = "/auth/register", produces = "application/json")
@Transactional
public class RegistrationController {

    @PersistenceContext
    EntityManager entityManager;

    @PostMapping
    @ResponseStatus(HttpStatus.CREATED)
    public Map<String, String> registerNewUser(@RequestBody @Valid User newUser) {

        try {
            entityManager.persist(newUser);
            entityManager.flush();
        } catch (PersistenceException ex) {
            // 解析异常以找出违反的约束是重复的用户名、电子邮件还是两者都有
            String message = parseExceptionForConstraintNames(ex);
            throw new ResponseStatusException(HttpStatus.CONFLICT, message);
        }
		
        return Collections.singletonMap("message", "Success..."); 
    }

}

选项#2:CrudRepository的自定义@Query

@RestController
@RequestMapping(path = "/auth/register", produces = "application/json")
public class RegistrationController {

    private final UsersRepository usersRepository;

    @Autowired
    public RegistrationController(UsersRepository usersRepository) {
        this.usersRepository = usersRepository;
    }

    @PostMapping
    @ResponseStatus(HttpStatus.CREATED)
    public Map<String, String> registerNewUser(@RequestBody @Valid User newUser) {

        try {
            usersRepository.insert(newUser);
        } catch (DataIntegrityViolationException ex) {
            // 解析异常以找出违反的约束是重复的用户名、电子邮件还是两者都有
            String message = parseExceptionForConstraintNames(ex);
            throw new ResponseStatusException(HttpStatus.CONFLICT, message);
        }
		
        return Collections.singletonMap("message", "Success..."); 
    }

}
public interface UsersRepository extends CrudRepository<User, String> {

    @Modifying
    @Transactional
    @Query(nativeQuery = true, value = "INSERT INTO users (username, email) " +
            "VALUES (:#{#user.username}, :#{#user.email})")
    void insert(@Param("user") User newUser);

}
英文:

Please, look at the two code examples bellow which I'm going to use in my Spring Boot project. They both do merely the same thing - add a new object into users table, represented by User entity with username defined as @Id and a unique constraint imposed on email column (there are some other columns as well, but they are not shown here for brevity). Note: I can't simply use save() method from CrudRepository, because it merges existing record with new object if they both have the same username value. Instead, I need to insert a new object with appropriate exception thrown for duplicate data persistence.

My question is about which option should be given a favor. With EntityManager, I don't need to construct SQL statement. Apart from that obvious observation, are there any advantages which one method may offer over the other (especially, in the matter of performance and resources consumption)?

Also, when I read latest books and tutorials about data persistence in Spring Boot, they mainly focus on Spring Data JPA. For example, the 5th edition of "Spring in Action" has no word about Hibernate's EntityMnager. Does it mean that dealing with Hibernate directly can be regarded as kind of "old school" and should generally be avoided in modern projects?

Option #1: Hibernate's EntityManager

@RestController
@RequestMapping(path = &quot;/auth/register&quot;, produces = &quot;application/json&quot;)
@Transactional
public class RegistrationController {

    @PersistenceContext
    EntityManager entityManager;

    @PostMapping
    @ResponseStatus(HttpStatus.CREATED)
    public Map&lt;String, String&gt; registerNewUser(@RequestBody @Valid User newUser) {

        try {
            entityManager.persist(newUser);
            entityManager.flush();
        } catch (PersistenceException ex) {
			// parse exception to find out which constraints have been 
			// broken - either it&#39;s duplicate username, email or both
			String message = parseExceptionForConstraintNames(ex);
            throw new ResponseStatusException(HttpStatus.CONFLICT, messsage);
        }
		
        return Collections.singletonMap(&quot;message&quot;, &quot;Success...&quot;); 
    }

}

Option #2: custom @Query from CrudRepository

@RestController
@RequestMapping(path = &quot;/auth/register&quot;, produces = &quot;application/json&quot;)
public class RegistrationController {

    private final UsersRepository usersRepository;

    @Autowired
    public RegistrationController(UsersRepository usersRepository) {
        this.usersRepository = usersRepository;
    }

    @PostMapping
    @ResponseStatus(HttpStatus.CREATED)
    public Map&lt;String, String&gt; registerNewUser(@RequestBody @Valid User newUser) {

        try {
            usersRepository.insert(newUser);
        } catch (DataIntegrityViolationException ex) {
			// parse exception to find out which constraints have been 
			// broken - either it&#39;s duplicate username, email or both
			String message = parseExceptionForConstraintNames(ex);
            throw new ResponseStatusException(HttpStatus.CONFLICT, message);
        }
		
        return Collections.singletonMap(&quot;message&quot;, &quot;Success...&quot;); 
    }

}
public interface UsersRepository extends CrudRepository&lt;User, String&gt; {

    @Modifying
    @Transactional
    @Query(nativeQuery = true, value = &quot;INSERT INTO users (username, email) &quot; +
            &quot;VALUES (:#{#user.username}, :#{#user.email})&quot;)
    void insert(@Param(&quot;user&quot;) User newUser);

}

答案1

得分: 2

参见此回答,了解使用 JPA 存储库与实体管理器。

最佳实践是不直接使用存储库。在 controllerrepository 之间使用 Service 层,在这里您可以通过使用 findByUsername(String username); 来实现逻辑以检查记录在数据库中是否已存在,如果已存在则抛出异常,否则在数据库中进行 save() 操作。

英文:

See this answer for Using JPA repository vs Entity Manager.

Best practice is to not use Repository directly. use Service layer between controller and repository where you can implement the logic for duplicate entries by checking if the record already exist in DB using findByUsername(String username); throw exception if it already exist else save() the object in DB

答案2

得分: 1

根据给定的要求,实体中的“username”字段从未符合“@Id”的条件。

为什么不能添加一个显式的id字段,并为id字段添加一个序列生成器,然后只将“username”标记为“unique”约束呢?

英文:

With the given requirements, the username filed in the entity never qualifies for the @Id.

Why can't u add an explicit id field with some sequence generator for the id filed and just keep the username marked with unique constraint only.

huangapple
  • 本文由 发表于 2020年9月18日 20:13:20
  • 转载请务必保留本文链接:https://go.coder-hub.com/63955576.html
匿名

发表评论

匿名网友

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

确定