【问题标题】:Does JPA/EntityManager provide a mechanism similar to Spring's RowMapperJPA/EntityManager 是否提供类似于 Spring 的 RowMapper 的机制
【发布时间】:2015-07-17 06:13:41
【问题描述】:

我正在致力于标准化遗留代码库和新开发以使用 EntityManager 进行持久性。然而,为了成功,我需要提供分解为原生 SQL 并手动处理结果的能力。 JPA EntityManager 几乎通过以下方式提供此功能:

em.createNativeQuery("select ... from my_table where ...");

但我缺少的是执行 Spring 风格的 RowMappers 的能力。我可以看到有一些机制很接近。我当然可以使用注释、xml 等映射实体,但我需要将代码放入结果集中每一行的处理中,所以这不是一个选项。我可以看到我可以传入 SQLResultSetMapping,但据我所知,这再次仅支持元数据映射。理想情况下,我需要以下内容:

em.createNativeQuery("select ... from my_table where ...",  
  new RowMapper {  
    public Object mapRow(ResultSet rs, int rowNum) throws SQLException {  
         MyObject o = new MyObject();  
         o.setMyCustomProperty( rs.get...() );  
         //...  
         return b;  
    }  
  }  
); 

是否存在与上述等价的内容?我可以构造一个 SQLResultSetMapping 来完全控制上面的映射吗?

如果没有,我是否可以至少以标准方式获取 EM 底层的数据源,以便我可以使用 JdbcTemplate 包装它并且没有两条路径来配置数据源?

DataSource ds = em.getDataSource();  
JdbcTemplate t = new JdbcTemplate(ds);  

感谢您的帮助。

【问题讨论】:

  • 您的 SQL 查询返回一个 List。您只需要一个 for 循环即可将每个数组映射到一个对象。 RowMapper 并没有做更多的事情。
  • 是的,谢谢。我试图避免这种情况,以避免在结果集上循环两次的开销。我假设 JPA 堆栈迭代结果集以构建 List,然后我的层将再次执行相同的操作来构建对象。我希望有一些 API 可以用来将自己注入到那个循环中。
  • List 仅具有我们需要通过索引获取的行值,如果将来列的顺序发生更改,这可能会产生问题,但在 RowMapper 中我们可以获取值通过从结果集中传递 column_names 以便更改列顺序不会影响,那么在 JPA 中是否有任何实现?

标签: java spring hibernate jpa entitymanager


【解决方案1】:

实现此目的的一种方法是通过 Hibernate 会话(前提是您使用的是 Hibernate)并使用其https://docs.jboss.org/hibernate/core/3.3/api/org/hibernate/transform/ResultTransformer.html。代码应如下所示,您不需要遍历列表两次:

List<MyDto> result = entityManager.unwrap(org.hibernate.Session.class)
        .createSQLQuery("select ... from my_table where ...")
        .setParameter("my_param", "my_param_value")
        .setResultTransformer(org.hibernate.transform.Transformers.aliasToBean(MyDto.class))
        .list();

希望这会有所帮助。

【讨论】:

    【解决方案2】:

    JPA原生查询结果可以映射到DTO POJO类:

    • DTO POJO 类

      @lombok.Getter
      @lombok.AllArgsConstructor
      public class MyDto {
          private String x;
          private Long y;
      }
      
    • 存储库 bean:

      @Repository
      public class MyRepository {
      
          @PersistenceContext private EntityManager em;
      
          static final String MY_SQLMAP = "My-SQL-Mapping";
      
          public List<MyDto> findMy() {
              Query query = em.createNativeQuery("select x, y from my_table", MY_SQLMAP);
              return query.getResultList();
          }
      
          @SqlResultSetMapping(name= MY_SQLMAP, classes = {
              @ConstructorResult(targetClass = MyDto.class,
                  columns = {
                      @ColumnResult(name="x",type = String.class),
                      @ColumnResult(name="y",type = Long.class)
                  }
              )
          }) @Entity class MyCfgEntity{@Id int id;} // <- walkaround
      
      }
      

    【讨论】:

      【解决方案3】:

      如果您不想拥有该解决方法实体,另一种方法是将其放在任何有效实体之上,例如

      @Entity
      @SqlResult(...)
      

      但是,您将添加与您的实体并不真正相关的映射。不幸的是,如果不在实体上,JPA(作为 JPA 2.1)将不会处理 SqlResult 注释。另一种选择是将其添加到 XML ORM 映射文件中,类似这样。

      <?xml version="1.0" encoding="UTF-8" ?>
      
      <entity-mappings xmlns="http://xmlns.jcp.org/xml/ns/persistence/orm"
                       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
                       xsi:schemaLocation="
                       http://xmlns.jcp.org/xml/ns/persistence/orm
                       http://xmlns.jcp.org/xml/ns/persistence/orm_2_1.xsd" version="2.1">
      
          <sql-result-set-mapping name="MySummaryResultMapping">
              <constructor-result target-class="model.MySummaryResult">
                  <column name="x" class="java.lang.String" />
                  <column name="average" class="java.lang.Double"/>
                  <column name="foo" class="java.lang.Double"/>
                  <column name="bar" class="java.lang.Integer"/>
              </constructor-result>
          </sql-result-set-mapping>
      </entity-mappings>
      

      然后在设置实体管理器时在 spring 上下文中加载该映射 XML

      <bean id="myEntityManagerFactory"
                class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
          <property name="dataSource" ref="myDataSource"/>
          <property name="packagesToScan" value="model"/>
          <property name="mappingResources" value="META-INF/persistenceMapping.xml" />
          ...
      </bean>
      

      这样你可以做这样的事情并使用 POJO 来返回查询结果。

      @Component
      public class MyReportDAO {
      
      @PersistenceContext
      private EntityManager entityManager;
      
      public MySummaryResult calculateReportFoo(String foo, LocalDate startDay, LocalDate endDay) {
          Query query = entityManager.createNativeQuery("SELECT x,  AVG(bar_column)  AS average, SUM(foo) AS foo," +
                  " bar " +
                  " FROM My_table WHERE FOO= :foo AND bar_DATE BETWEEN :startDay AND :endDay" +
                  " GROUP BY x,bar", "MySummaryResultMapping");
          query.setParameter("foo", foo);
          query.setParameter("startDay", startDay);
          query.setParameter("endDay", endDay);
          return (MySummaryResult) query.getSingleResult();
        }
      }
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2013-03-31
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2012-03-06
        • 2011-04-11
        相关资源
        最近更新 更多