JPA 结果集映射

上一章节介绍了 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 注解。

@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&amp;
                      serverTimezone=UTC&amp;useUnicode=true&amp;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}

@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 {
    // 省略...
}
说说我的看法
全部评论(
没有评论
关于
本网站专注于 Java、数据库(MySQL、Oracle)、Linux、软件架构及大数据等多领域技术知识分享。涵盖丰富的原创与精选技术文章,助力技术传播与交流。无论是技术新手渴望入门,还是资深开发者寻求进阶,这里都能为您提供深度见解与实用经验,让复杂编码变得轻松易懂,携手共赴技术提升新高度。如有侵权,请来信告知:hxstrive@outlook.com
公众号