为什么使用JPA而不是直接在Java文件上编写SQL查询(即直接写入JDBC)?

时间:2010-12-10 06:32:19

标签: java database orm jpa

我一直在阅读几篇文章JPA (Java Persistent API)以及哪些供应商支持它(DataNucleus,JBoss Hibernate等)

我没有ORM(对象关系映射)的经验。

到目前为止,我所做的是使用DTO和DAO编写自己的数据库类。到目前为止,我很高兴我所拥有但想知道为什么人们使用JPA而不是包含SQL的Java文件

对我来说,我觉得写DAO课程就像下面这样。

public class DAOUsers {
     public void insertNewUser(DTO DtoUser) {
           String query = "INSERT INTO users(username, address) " +
                          "VALUES(DtoUser.username , DtoUser.address)";
           Executor.run(query);
     }

}

我已经了解到JPA使用JPQL,Java持久查询语言并且它针对实体对象进行操作 而不是直接使用db表。

我的理解(纠正我,如果我错了)是这里的实体对象和我的DTO对象一样(有点像bean吗?)

但无论如何...... JPA真正有益于在我的文件中编写纯SQL吗? 看起来像使用JPA所需的注释并使SQL不可读对我来说不是很有吸引力..

如果您需要更多说明,请告诉我,我是这个主题的新手,并希望听到一些意见。

9 个答案:

答案 0 :(得分:39)

  

为什么要使用JPA而不是直接使用JPA   在Java文件上编写SQL查询(即   直接到JDBC)?

某些项目要求工程师更多地关注对象模型,而不是用于访问数据存储的实际SQL查询。问题实际上可以解释为

  

为什么要使用ORM框架?

在不同的背景下可以有不同的答案。

大多数项目都可以从拥有域模型中受益,持久性是第二个问题。使用JPA(实现)或大多数其他ORM框架,可以将所有实体(即数据库中的表)建模为Java中的类。此外,还可以将行为嵌入到这些类中,从而实现行为丰富的域模型。此模型中的实体可以有多种用途,包括替换DTO以跨层传输数据的目的。

也就是说,有些地方的ORM框架可能不是直接适应问题的,特别是当数据模型已经建立时,或者正在使用遗留系统时,将数据库表映射到Java类是非琐碎的运动。在某些情况下,如果需要绝对调整ORM框架生成的SQL,那么ORM框架通常是不合适的。

相关问题

  1. Java EE Architecture - Are DAO's still recommended when using an ORM like JPA 2?
  2. Using an ORM or plain SQL?
  3. ORM vs Handcoded Data Access Layer

答案 1 :(得分:23)

  

JPA在我的文件中编写纯SQL的真正好处是什么?

以下是一些好处:

  • JPA允许您避免在SQL的数据库特定方言中编写DDL。相反,您可以用XML编写“映射”,或使用Java注释。

  • JPA允许您避免在SQL的数据库特定方言中编写DML。

  • JPA允许您加载和保存Java对象和图形,而不需要任何DML语言。

  • 需要执行查询时,JPQL允许您根据Java实体而不是(本机)SQL表和列来表达查询。

一般来说,JPA比JDBC + SQL +手写映射更简单,更清晰,劳动强度更低。数据模型越复杂,它就越有益。

但是,如果性能是覆盖问题,JPA确实会通过在应用程序和数据库之间添加层来阻碍性能。如果您的应用程序要求您广泛手动优化本机数据库查询和模式以最大限度地提高性能,那么JPA可能不太合适。

如果你在同一个应用程序中处理Java,JDBC和SQL,而不是让ORM处理凌乱的细节,那么JPA可能也不适合你。 (但如果你是,你可能是少数......)

答案 2 :(得分:15)

这是一个非常常见的问题,因此此答案基于我在博客上写的more detailed article

JDBC很详细

例如,这是插入某些记录的最常用方法:

int postCount = 100;

try (PreparedStatement postStatement = connection.prepareStatement("""
    INSERT INTO post (
        id,
        title 
    ) 
    VALUES (
        ?, 
        ?
    )
    """
)) {
    for (int i = 1; i <= postCount; i++) {        
        int index = 0;
        
        postStatement.setLong(
            ++index, 
            i
        );
        postStatement.setString(
            ++index, 
            String.format(
                "High-Performance Java Persistence, review no. %1$d", 
                i
            )
        );
        
        postStatement.executeUpdate();
    }
} catch (SQLException e) {
    fail(e.getMessage());
}

JDBC批处理需要更改您的数据访问代码

而且,当您意识到此方法效果不佳时,因为您忘记了使用批处理,因此必须更改以前的实现,如下所示:

int postCount = 100;
int batchSize = 50;

try (PreparedStatement postStatement = connection.prepareStatement("""
    INSERT INTO post (
        id,
        title 
    ) 
    VALUES (
        ?, 
        ?
    )
    """
)) {
    for (int i = 1; i <= postCount; i++) {
        if (i % batchSize == 0) {
            postStatement.executeBatch();
        }
        
        int index = 0;
        
        postStatement.setLong(
            ++index, 
            i
        );
        postStatement.setString(
            ++index, 
            String.format(
                "High-Performance Java Persistence, review no. %1$d", 
                i
            )
        );
        
        postStatement.addBatch();
    }
    postStatement.executeBatch();
} catch (SQLException e) {
    fail(e.getMessage());
}

JPA和Hibernate替代方案

使用JPA,一旦您映射了您的实体:

@Entity
@Table(name = "post")
public class Post {

    @Id
    private Long id;

    private String title;

    public Long getId() {
        return id;
    }

    public Post setId(Long id) {
        this.id = id;
        return this;
    }

    public String getTitle() {
        return title;
    }

    public Post setTitle(String title) {
        this.title = title;
        return this;
    }
}

然后,您设置以下Hibernate配置属性:

<property name="hibernate.jdbc.batch_size" value="50"/>

这是您可以插入那些post表记录的方法:

for (long i = 1; i <= postCount; i++) {
    entityManager.persist(
        new Post()
            .setId(i)
            .setTitle(
                String.format(
                    "High-Performance Java Persistence, review no. %1$d", 
                    i
                )
            )
    );
}

简单得多,对吧?

使用JDBC获取数据

使用JDBC,这是执行SQL投影的方式:

int maxResults = 10;

List<Post> posts = new ArrayList<>();

try (PreparedStatement preparedStatement = connection.prepareStatement("""
        SELECT 
            p.id AS id, 
            p.title AS title
        FROM post p 
        ORDER BY p.id
        LIMIT ?
        """
)) {
    preparedStatement.setInt(1, maxResults);

    try (ResultSet resultSet = preparedStatement.executeQuery()) {
        while (resultSet.next()) {
            int index = 0;
            
            posts.add(
                new Post()
                    .setId(resultSet.getLong(++index))
                    .setTitle(resultSet.getString(++index))
            );
        }
    }

} catch (SQLException e) {
    fail(e.getMessage());
}

这很冗长,因为您必须将ResultSet转换为应用程序正在使用的数据结构(例如DTO,JSON Web响应)。

使用JPA获取数据

使用JPA,您可以像这样提取List条记录中的Post

int maxResults = 10;

List<Post> posts = entityManager.createQuery("""
    select p
    from post p 
    order by p.id
    """, Post.class)
.setMaxResults(maxResults)
.getResultList();

而且,不仅编写起来更简单,而且它在Hibernate支持的每个数据库上都可以使用,因为分页语法是根据基础数据库方言改编的。有关此主题的更多详细信息,请查看this article

JPA与JDBC相比的其他优势

  • 您可以获取实体或DTO。您甚至可以获取hierarchical parent-child DTO projection
  • 您可以在不更改数据访问代码的情况下启用JDBC批处理。
  • 您支持乐观锁定。
  • 您有一个悲观的锁定抽象,它独立于底层的特定于数据库的语法,因此您可以获得READ and WRITE LOCK甚至是SKIP LOCK
  • 您有一个与数据库无关的分页API。
  • 您可以向IN查询子句提供List的值,如this article中所述。
  • 您可以使用一个高度一致的缓存解决方案,该解决方案允许您卸载主要节点,对于读写事务,该节点只能被垂直调用。
  • 您内置了对通过Hibernate Envers进行审核日志记录的支持。
  • 您对multitenancy具有内置支持。
  • 您可以使用Hibernate hbm2ddl工具从实体映射生成初始模式脚本,该工具可以提供给自动模式迁移工具,例如Flyway
  • 不仅您可以自由执行任何native SQL query,而且可以使用SqlResultSetMapping将JDBC ResultSet转换为JPA实体或DTO。

JPA的缺点

使用JPA和Hibernate的缺点如下:

  • 尽管开始使用JPA非常容易,但是成为一名专家需要大量时间投入,因为除了阅读其手册之外,您还必须了解数据库系统的工作方式,SQL标准以及您所使用的特定SQL风格项目关系数据库。
  • 有些不太直观的行为可能会让初学者感到惊讶,例如flush operation order
  • Criteria API非常冗长,因此您需要使用Codota之类的工具来更轻松地编写动态查询。

结论

关于Java生态系统的最伟大的事情之一就是丰富的高质量框架。如果JPA和Hibernate不适合您的用例,则可以使用以下任何框架:

  • MyBatis,这是一个非常轻量级的SQL查询映射器框架。
  • QueryDSL,可让您动态构建SQL,JPA,Lucene和MongoDB查询。
  • jOOQ,它为基础表,存储过程和函数提供了Java元模型,并允许您使用非常直观的DSL以类型安全的方式动态构建SQL查询。

尽管JPA具有许多优点,但是如果JPA和Hibernate不能很好地满足当前应用程序的需求,那么您可以使用许多其他高质量的替代方法。因此,如今,除非您正在开发数据访问框架,否则您实际上并不需要使用纯JDBC。

正如我在this free sample of my High-Performance Java Persistence book中所述,您甚至不必在JPA或其他框架之间进行选择。您可以轻松地将JPA与jOOQ之类的框架结合使用,以充分利用两者的优势。

答案 3 :(得分:14)

虽然这是一个老问题,但我觉得它值得一个新答案。我是JPA的后期使用者,我已经使用它几年了,虽然我有一些时间让我对于新应用程序的简单性感到印象深刻,但我已经变得毫无印象具有正确执行JPA所需的性能,复杂性和学习曲线。这个帖子的答案实际上强化了我的立场。

首先,@ chicket建议“这些实体可以有多种用途”......我在制作中看到过这种情况,而且我会说ORM会鼓励它。凝聚力和单一的责任主体。根据我的经验,向数据库实体添加行为是一件麻烦事。我知道这是因为我已经做到了,并且后悔了。

其次,JPA的复杂性有一些简单的替代方法,它们能够使用具有RDBMS的类,而不会由于ORM尝试(不成功)解决的不匹配而导致的所有沉重(和性能问题)。我们在具有超过1,000个表的应用程序中使用非JPA关系类映射工具已经十年了,我们根本看不到JPA是如何改进而不是更直接地访问数据库。 JPA模糊了数据库的功能,同时将开销(以注释和JQL的形式)添加到类模型中...不应该以其他方式工作吗?

@water提出了许多在理论上属实但在现实中不切实际的事情。例如,我已经三次切换后端数据库,我可以向读者保证,没有一些配置调整,你就完成了。我建议,如果你花了很多时间维护你的持久层,你的数据库模型正在发展,你将在JPA中做相同或更多的工作。特别是当JPA中的非平凡查询需要使用JQL!

几乎每个人都假装JPA开发人员不需要知道SQL。我在实践中看到的是,现在我们必须学习SQL JQL。显然我们不需要做DDL - 但是在任何非常重要的应用当然你需要知道DDL。 Hibernate甚至不推荐使用自动DDL生成。显然我们不需要做DML,除非我们调用Native Query,当然这是不可移植的,它会破坏缓存,并且与JDBC有同样的问题...

最终,在一个结构合理的应用程序中,域模型独立于业务逻辑,JPA几乎没有提供功能,因为我发现这是一个非常高的学习曲线 - 因为域模型实际上非常易于构建。我不会直接使用JDBC,但是像Apache DBUtils这样的东西在JDBC之上提供了一个简单的层,它将行映射到对象,并且通过一些努力可以提供JPA的大部分优点,没有任何隐藏,也没有任何开销。

我从JDBC 1.0开始使用各种库(以及JDBC之前的iODBC和ESQL)开发Java数据库应用程序,仅出于性能原因,我已经完成了JPA。但即使表现更好,学习曲线和不完整的抽象也会给我带来严重的停顿。 JPA很复杂,并试图隐藏我认为开发人员真正需要关注的细节。作为一个例子,我们最近看到hibernate问题250删除命令到数据库时,一个就足够了。 JPA本质上使这种错误变得容易。

我不是在倡导JDBC,我只是在提倡反对JPA。不能或不能在SQL中工作的开发人员可能不应该编写关系应用程序 - 除了我这样的开发人员,他们不能用矩阵代数来挽救我的生命,应该是编写3D游戏。那些使用SQL谋生的开发人员应该为扭曲的SQL而感到恐惧,因为它会向服务器发送休眠状态,以避免一开始就不需要的往返。

答案 4 :(得分:6)

如果操作正确,您可以使用JPA实现(如hibernate)将SQL查询直接映射到java对象。我最近做了一个项目,我有一个POJO和3或4个注释和一些设置代码来获取存储过程并将其直接映射到对象列表(POJO类类型)。这对我来说是JPA力量的一部分。

如果您使用它就像直接使用SQL + JDBC那么我不知道有什么优点。

关于JPA的优点,有一篇不错的文章here

希望这有帮助。

答案 5 :(得分:6)

众所周知,对象是我们生活中最重要的事情之一,在编程对象中很容易简化任何问题.... 如果对象在那里可用那么为什么我们使用整个东西而不是那个东西的那个小部分意味着对象......

  • 如果你手工编写SQL语句 在您的企业应用程序中,您 正在花费大量的 你的开发时间更新和 维护你的持久层。

持久性,==&gt;不再需要JDBC API来进行结果集或数据处理。 ==&gt;它有助于减少代码行, &安培;&安培;&安培;&安培;&安培;&安培; ==&gt;它将我们的应用程序从基础SQL数据库和sql方言中抽象出来。切换到其他SQL数据库需要在Hibernate配置文件中进行少量更改(一次写入/随处运行)。

答案 6 :(得分:1)

  • JPA非常适合非面向性能的复杂应用程序。
  • 在性能是关键绩效指标的情况下,JDBC是最合适的选择。

答案 7 :(得分:0)

此外,正如你所知道Java的口号:“写一次,到处运行”

使用JPQL,您可以在每个数据库(DB2,Oracle等)上执行JPQL查询。

答案 8 :(得分:0)

使用JDBC时,我们有一个映射层将DB连接的表映射到业务对象。但是JPA以一种方式或其他方式来使数据对象分别维护,这增加了维护工作和DB表重构的额外工作量。

也同意https://stackoverflow.com/a/57766861/3343801