- 内容提要
- 序
- 前言
- 第一部分 背景知识
- 第 1 章 Spring Data 项目
- 第 2 章 Repository:便利的数据访问层
- 第 3 章 使用 Querydsl 实现类型安全的查询
- 第二部分 关系型数据库
- 第 4 章 JPA Repository
- 第 5 章 借助 Querydsl SQL 实现类型安全的 JDBC 编程
- 第三部分 NoSQL
- 第 6 章 MongoDB: 文档存储
- 第 7 章 Neo4j:图数据库
- 第 8 章 Redis:键/值存储
- 第四部分 快速应用开发
- 第 9 章 使用 Spring Roo 实现持久层
- 第 10 章 REST Repository 导出器
- 第五部分 大数据
- 第 11 章 Spring for Apache Hadoop
- 第 12 章 使用 Hadoop 分析数据
- 第 13 章 使用 Spring Batch 和 Spring Integration 创建大数据管道
- 第六部分 数据网格
- 第 14 章 分布式数据网格:GemFire
- 关于封面
5.3 执行查询
现在,通过实现 CustomerRepository 中的查询方法,来看一下如何使用 QueryDslJdbc Template 执行查询。
5.3.1 Repository 实现起步
实现类将会自动装配 DataSource;在这个设置中,将创建 QueryDslJdbcTemplate 以及针对表中各列的投射,当获取 Customer 实例所需的数据时,这个投射会被所有的查询用到,如示例 5-6 所示。
示例 5-6 构建 QueryDslCustomerRepository 实例
我们正在编写的是存储类,所以开始添加了 @Repository 注解。这是一个标准的 Spring 通用注解,这样,组件在类路径扫描的时候能够被发现。除此之外,如果使用 ORM 风格的数据访问技术来实现存储类,那么它还会为存储类进行异常的转换,也就是从特定 ORM 的异常转换为 Spring 的 DataAccessException 异常体系。在我们的场景中,所使用的是基于模板的方式,模板本身会提供这种异常转换功能。
接下来是 @Transactional 注解。这也是一个标准的 Spring 注解,它表明对这个类中所有方法的调用都会包装在数据库事务之中。只要在配置中包含了事务管理器实现,那我们就不用关心在存储类的代码中启动和完成事务了。
我们还定义了对两个所生成的查询类型的引用,也就是 QCustomer 和 QAddress。在数组 customerAddressProjection 中为查询保存了 Querydsl Path 条目,每个 Path 对应于要查询的一列。
构造器添加了 @Autowired 注解,这意味着当 Repository 实现在进行配置的时候,容器将会把应用上下文中所定义的 DataSource 注入进来。这个类剩余的部分由 CustomerRepository 中所定义的方法组成,我们需要为其提供实现,那现在就开始吧。
5.3.2 查询单个对象
首先,实现 findById 方法,如示例 5-7 所示。要用来进行查找的 ID 是作为唯一的参数传递进来的。因为这是只读的方法,添加 @Transactional(readOnly = true) 注解以给出一个提示,一些 JDBC 驱动就可以利用它来提升事务处理的能力。即便有的 JDBC 驱动不会使用它,对只读的方法添加这样一个可选的属性也不会有什么负面影响。
示例 5-7 查询单个对象
首先,创建一个 SQLQuery 实例。正如前文所述,当使用 QueryDslJdbcTemplate 的时候,需要让模板来管理 SQLQuery 实例,这就是为什么需要用静态工厂方法 newSqlQuery() 来获得实例。SQLQuery 类提供了流畅的接口,每个方法又会返回这个 SQLQuery 实例。这样就能将多个方法连接在一起,代码会更易阅读。使用 from 方法指明要查询的主表。然后,使用 leftJoin(...) 方法添加对 address 表的左连接。表 address 和 customer 之间所有匹配外键的 address 行会被包含进来。如果没有匹配的,那么在返回的结果集中 address 列为 null。如果不止有一个 address,那么对于每个 customer 将会得到多行的 address。在稍后将其映射为 Java 对象时,这是必须要进行处理的。SQLQuery 的最后一部分使用 where 方法来声明断言并且所提供的条件是 id 列必须等于 id 参数。
SQLQuery 创建完成之后,通过 QueryDslJdbcTemplate 的 queryForObject 方法执行查询,在调用的时候要将 SQLQuery 和映射器及投射一起传递进去,在我们的例子中,也就是之前所创建的 ResultSetExtractor 和 customerAddressProjection。前面曾经提到过,因为我们的查询包含了 leftJoin,所以需要处理每个 Customer 拥有多行的潜在可能性。
示例 5-8 是 CustomerExtractor 的实现。
示例 5-8 用于单个对象的 CustomerExtractor
可以看到,CustomerListExtractor 用来抽取 Customer 对象的 List,如果 List 中存在对象的话会返回里面第一个对象,如果 List 为空的话,将会返回 null。在 CustomerList Extractor 的构造器中,因为将 expectedResults 设置为 OneToManyResultSetExtractor. ExpectedResults.ONE_OR_NONE,所以我们能够知道结果不会超过一个。
5.3.3 OneToManyResultSetExtractor 抽象类
在查看 CustomerListExtractor 之前,先看一下它的基类,这是由 Spring Data JDBC 扩展项目所提供的特殊实现 OneToManyResultSetExtractor。示例 5-9 给出了 OneToManyResultSetExtractor 的概览。
示例 5-9 用于抽取对象 List 的 OneToManyResultSetExtractor 的概览
OneToManyResultSetExtractor 扩展自 ResultSetExtractor 并以 List<T>作为返回类型。方法 extractData 负责迭代结果集并抽取行数据。OneToManyResultSetExtractor 有 3 个抽象的方法必须由子类来实现,也就是 mapPrimaryKey、mapForeign Key 和 addChild。当迭代结果集时,这些方法用来识别主键和外键,这样就能识别何时会有新的根对象并且会帮助我们将映射的子对象添加到根对象之中。
OneToManyResultSetExtractor 类也需要 RowMapper 实现,以便为根对象和子对象提供所需要的映射。
5.3.4 CustomerListExtractor 实现
现在,继续看一下实际的 CustomerListExtractor 实现,它负责抽取 customer 和 address 的结果,如示例 5-10 所示。
示例 5-10 用于抽取对象 List 的 CustomerListExtractor 实现
CustomerListExtractor 扩展自 OneToManyResultSetExtractor,它调用了超类的构造方法并将所需的映射器传递进来,也就是为 Customer 类使用的 CustomerMapper(它是一对多关系的根元素)和为 Address 类使用的 AddressMapper(它是同一个一对多关系的子元素)。
除了这两个映射器,我们还需要为抽象的 OneToManyResultSetExtractor 类提供 mapPrimaryKey、mapForeignKey 和 addChild 方法的实现。
接下来,看一下所使用的 RowMapper 实现。
5.3.5 RowMapper 的实现类
这里所使用的 RowMapper 与常规 JdbcTemplate 中所使用的是一样的。它们实现了一个名为 mapRow 的方法,这个方法以 ResultSet 和行号作为参数。使用 QueryDslJdbcTemplate 所带来的唯一区别在于不需要使用字符串常量访问列了它使用查询类型来引用列的标签(label)。在 CustomerRepository 中有一个静态的方法,这个方法通过 Path 的 toString 方法来获取标签:
因为将 RowMapper 实现为静态内部类,所以它们可以访问这个私有的静态方法。
首先来看一下 Customer 对象的映射器,如示例 5-11 所示,通过 qCustomer 来指明列,这个对象引用的是 QCustomer 查询类型。
示例 5-11 为 Customer 所提供的根 RowMapper 实现
接下来看一下 Address 对象的映射器,它使用 qAddress 引用 QAddress 查询类型,如示例 5-12 所示。
示例 5-12 为 Address 所提供的子 RowMapper 实现
因为 Address 类具备所有属性的设置方法,所以我们原本可以使用 Spring 的 BeanPropertyRowMapper 而不必提供自定义的实现。
5.3.6 查询对象列表
当查询对象的列表时,与查询单个对象的过程是几乎是一样的,唯一的区别在于可以直接使用 CustomerListExtractor 了,而不必对其进行包装,也不必再返回 List 中的第一个对象,如示例 5-13 所示。
示例 5-13 查询对象列表
我们创建了一个 SQLQuery 并使用了 from(...) 和 leftJoin(...) 方法,但是这一次没有提供断言因为我们要返回所有的 customer。当执行这个查询时,直接使用了 CustomerListExtractor 以及前面所用到的 customerAddressProjection。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论