上一章节介绍了 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 注解。
jpa广告位
@SqlResultSetMapping 注解
该注解用来指定本地 SQL 查询结果的映射,即查询结果中某一个列对应 Java 对象中的哪个属性。注解支持的属性如下:
String name
指定结果集映射的名称,在 Query API 的方法中使用。
EntityResult[] entities
用来指定将结果集映射到哪个实体中,下面通过 @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)
)
ColumnResult[] columns
将结果集直接映射到标量中,例如:
@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 {
@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}
@SqlResultSetMappings 注解
该注解用来指定一个或者多个 @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 {
// 省略...