上一章节介绍了 OpenJPA 本地查询,在本地查询的实例中,有如下查询代码:
Query query = em.createNativeQuery( "select id, name, age, salary from user where age<30"); List list = query.getResultList(); for (Object object : list) { Object[] objs = (Object[]) object; System.out.println("id=" + objs[0] + " name=" + objs[1] + " age=" + objs[2] + " salary=" + objs[3]); }
上面代码中,使用 createNativeQuery() 方法去执行本地 SQL 代码。SQL 执行成功后返回一个 List,List 的每一个元素是一个 Object 数组,数据库表中的每一行被当做一个 Object 数组。
于是,采用 for 语句通过数组下标的方式取出查询出来的所有数据。这看起来就很麻烦,还容易出错,OpenJPA 中有没有更好的方式呢?答案是肯定的,我们可以使用 JPA 结果映射来实现。下面将详细介绍结果映射相关的 @SqlResultSetMapping 和 @SqlResultSetMappings 注解。
该注解用来指定本地 SQL 查询结果的映射,即查询结果中某一个列对应 Java 对象中的哪个属性。注解支持的属性如下:
指定结果集映射的名称,在 Query API 的方法中使用。
用来指定将结果集映射到哪个实体中,下面通过 @FieldResult 注解手动指定 Java 对象属性到结果集列之间的映射。如下:
@SqlResultSetMapping(name = "userResult", entities = { @EntityResult(entityClass = User.class, fields = { @FieldResult(name = "id", column = "id"), @FieldResult(name = "username", column = "name"), @FieldResult(name = "age", column = "age"), @FieldResult(name = "salary", column = "salary") }) } )
如果 Java 对象属性名称和结果集列名称一致,则不需要使用 @@FieldResult 注解手动映射。如下:
@SqlResultSetMapping(name = "userResult", entities = { @EntityResult(entityClass = User.class, fields = { @FieldResult(name = "username", column = "name") }) } )
如果 Java 对象所有属性名称和结果集列名称一致,则根本不需要指定 fields 属性。如下:
@SqlResultSetMapping(name = "userResult", entities = { @EntityResult(entityClass = User.class) } )
将结果集直接映射到标量中,例如:
@SqlResultSetMappings({ @SqlResultSetMapping(name = "userResult", columns = { @ColumnResult(name = "id"), @ColumnResult(name = "name"), @ColumnResult(name = "age"), @ColumnResult(name = "salary"), @ColumnResult(name = "book_name"), @ColumnResult(name = "book_price") } ) })
上面结果集映射将输出如下数据:
[3,"用户-2",44,5855.36,"图书-用户-2-0",79.78651] [4,"用户-3",10,4105.1895,"图书-用户-3-0",33.370354]
对,你没有看错,就是将结果集每行映射成一个数组。
如果在注解中同时使用 columns 和 entities 属性,如下:
@SqlResultSetMappings({ @SqlResultSetMapping(name = "userResult", entities = { @EntityResult(entityClass = User.class) }, columns = { @ColumnResult(name = "book_name"), @ColumnResult(name = "book_price") } ) })
上面结果集映射将输出如下数据:
[{"age":44,"id":3,"salary":5855.36},"图书-用户-2-0",79.78651] [{"age":10,"id":4,"salary":4105.1895},"图书-用户-3-0",33.370354]
从上面日志可以看出,entities 属性指定的实体也被当做数组中的一个元素。
执行查询的 SQL 代码如下:
Query query = em.createNativeQuery( "select u.id, u.`name`, u.age, u.salary, " + " b.`name` as 'book_name', b.price as 'book_price' " + " from user u " + " join user_book ub on u.id=ub.USER_ID " + " join book b on b.id=ub.BOOKLIST_ID " + " where u.age < 50 and u.salary > 4000", "userResult"); List<Object> list = query.getResultList(); for (Object object : list) { System.out.println(JSONObject.toJSONString(object)); }
(1)添加 persistence.xml 配置
<persistence xmlns="http://java.sun.com/xml/ns/persistence" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="1.0"> <persistence-unit name="demo_ResultSetMapping" transaction-type="RESOURCE_LOCAL"> <!-- JPA提供者 --> <provider>org.apache.openjpa.persistence.PersistenceProviderImpl</provider> <!-- 声明实体类 --> <class>com.hxstrive.openjpa.annotation.demo_ResultSetMapping.User</class> <!-- 配置JPA数据库属性 --> <properties> <property name="openjpa.ConnectionURL" value="jdbc:mysql://localhost:3306/openjpa_learn?useSSL=false& serverTimezone=UTC&useUnicode=true&characterEncoding=UTF-8"/> <property name="openjpa.ConnectionDriverName" value="com.mysql.jdbc.Driver"/> <property name="openjpa.ConnectionUserName" value="root"/> <property name="openjpa.ConnectionPassword" value="aaaaaa"/> <property name="openjpa.Log" value="SQL=TRACE"/> <!-- 自动生成表 --> <property name="openjpa.jdbc.SynchronizeMappings" value="buildSchema(ForeignKeys=true)"/> <!-- 不使用加载时强化和编译时强化,使用运行时Unenhanced(不能发挥OpenJPA的最大效能,所以也不推荐) --> <property name="openjpa.ClassLoadEnhancement" value="false"/> <property name="openjpa.DynamicEnhancementAgent" value="false"/> <property name="openjpa.RuntimeUnenhancedClasses" value="supported"/> </properties> </persistence-unit> </persistence>
(2)定义实体对象,且在实体上面声明结果集映射。代码如下:
@Data @Entity @Table @SqlResultSetMappings({ @SqlResultSetMapping(name = "userResult", entities = { @EntityResult(entityClass = User.class, fields = { @FieldResult(name = "username", column = "name") }) } ) }) public class User { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Integer id; @Column private String username; @Column private Integer age; @Column private Float salary; // 省略了 toString() }
(3)客户端代码
import javax.persistence.*; import java.util.List; public class Demo { /** 持久化单元名称 */ private static final String PERSISTENCE_NAME = "demo_ResultSetMapping"; public static void main(String[] args) { EntityManagerFactory factory = Persistence.createEntityManagerFactory( PERSISTENCE_NAME, System.getProperties()); EntityManager em = factory.createEntityManager(); em.getTransaction().begin(); // 注意:结果映射在 User 上面声明 Query query = em.createNativeQuery( "select id, name, age, salary from user " + " where age < 50 and salary > 4000", "userResult"); List<User> list = query.getResultList(); for (User user : list) { System.out.println(user); } em.getTransaction().commit(); factory.close(); System.out.println("finished."); } }
输出日志如下:
select id, name, age, salary from user where age < 50 and salary > 4000 User{id=3, name='用户-2', age=44, salary=5855.36} User{id=4, name='用户-3', age=10, salary=4105.1895} User{id=10, name='用户-9', age=26, salary=6327.472}
该注解用来指定一个或者多个 @SqlResultSetMapping 注解,例如:
@Data @Entity @Table @SqlResultSetMappings({ @SqlResultSetMapping(name = "userResult", entities = { @EntityResult(entityClass = User.class, fields = { @FieldResult(name = "username", column = "name") }) } ), @SqlResultSetMapping(name = "userResult2", entities = { @EntityResult(entityClass = User.class) } ) }) public class User { // 省略... }