有 Java 编程相关的问题?

你可以在下面搜索框中键入要查询的问题!

java的JPA/EntityManager是否提供了类似于Spring的RowMapper的机制

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

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

但我缺少的是做Spring风格的RowMapper的能力。我可以看到,有几个机制是密切相关的。当然,我可以使用注释、xml等映射实体,但我需要将代码放入结果集中每一行的处理中,所以这不是一个选项。我可以看到我可以传入SqlResultsMapping,但据我所知,它同样只支持元数据映射。理想情况下,我需要的是:

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);  

谢谢你的帮助


共 (3) 个答案

  1. # 1 楼答案

    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
      
      }
      
  2. # 2 楼答案

    实现这一点的一种方法是通过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();
    

    希望这有帮助

  3. # 3 楼答案

    另一种方法是,如果您不想拥有该变通实体,则将其放在任何有效实体的顶部,例如

    @Entity
    @SqlResult(...)
    

    但是,您将添加一个与实体不相关的映射。不幸的是,如果SqlResult注释不在实体上,JPA(如JPA2.1)将不会处理该注释。另一种选择是将其添加到XMLORM映射文件中,类似这样

    <?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();
      }
    }