上一章节介绍了 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 {
// 省略...
}