了解 JDBC 层之 QueryDSL
一. 概述
我们开发的 java 应用,大部分都是基于数据库层面的业务开发;在行内,基本都是使用 Mybatis 的多,然而其对于代码的后期维护以及可读性来讲,没有 QueryDSL 来的好;这篇是围绕着 QueryDSL 的使用以及大体的原理进行介绍,详细的原理以及深度用法将会新的篇章去介绍。
抛开 QueryDSL,JDBC 层都应该包含哪些基本功能?
SQL 的拼装
反序列化操作
在开源的 JDBC 层框架,如 Mybatis,提供了如下特性
缓存 通过缓存,有效的减少与数据库的交互,提升的系统性能;一旦使用不规范,则会出现业务逻辑 bug。
丰富的结果映射(反序列操作)
二. 原理
QueryDSL 是采用 java 语言形式去编写 sql,然后交给框架自动生产 SQL 语言,接着执行;拿到执行结果,通过指定的序列化操作,我们可以拿到响应结果;具体查看我们的例子:
//获取指定的表对象,这个是通过querydsl-maven-plugin插件去扫描目标数据库获取的数据库元数据,从而生成的对象信息
QProduct product = QProduct.product;
//sqlQueryFactory是QueryDSL主要的入口,是用来专门构建SQL以及反序列的工厂类。
ProductVO productDetailResp = sqlQueryFactory.select(
//Projections.bean反序列后的目标对象ProductDetailResp
Projections.bean(ProductVO.class,
//下面的查询该表对象的字段列表
product.id, product.name, product.unitName, product.image,
product.sliderImage, product.cateId, product.price,
product.otPrice, product.cost, product.giveIntegral,
product.sort, product.addTime, product.status))
.from(product)//指定从那张表去查询
//下面是条件语句
.where(product.id.eq(productId))
//上面的语句只是填写相关的SQL生成规则以及反序列目标对象,下面语句才是真正的执行上面的两个动作;
//只拉取一条数据
.fetchOne();
执行后,生成的 SQL 如下:
select
product.id, product.name, product.unit_name, product.image,
product.slider_image, product.cate_id, product.price,
product.ot_price, product.cost, product.give_integral,
product.sort, product.add_time, product.status
from product product
where product.id = ?
limit ?
其中 limit 系统会自动填充为 2,接着会自动判断是否会出现多条,如果出现多条数据,则报错。
增删改操作例子如下
//插入操作
QProduct product = QProduct.product;
sqlQueryFactory.insert(product)
.set(product.id, uuidIdGenerator.generate())
.set(product.name, req.getName())
.set(product.unitName, req.getUnitName())
.execute();
//改操作
sqlQueryFactory.update(product)
.set(product.name, req.getName())
.set(product.unitName, req.getUnitName())
.set(product.image, req.getImage())
.set(product.sliderImage, req.getSliderImage())
.where(product.id.eq(req.getId()))
.execute();
//删除操作
sqlQueryFactory.delete(product)
.where(product.id.eq(req.getId()))
.execute();
现在我们看一下 QProduct 对象都包含了表对象,以及表中哪些字段以及类型信息;
public class QProduct extends com.querydsl.sql.RelationalPathBase<QProduct> {
private static final long serialVersionUID = 1266060615;
public static final QProduct product = new QProduct("product");
public final DateTimePath<java.time.LocalDateTime> addTime = createDateTime("addTime", java.time.LocalDateTime.class);
public final NumberPath<Short> cateId = createNumber("cateId", Short.class);
public final NumberPath<Long> cost = createNumber("cost", Long.class);
public final StringPath desc = createString("desc");
public final NumberPath<Long> giveIntegral = createNumber("giveIntegral", Long.class);
public final StringPath id = createString("id");
public final StringPath image = createString("image");
public final BooleanPath isBest = createBoolean("isBest");
public final BooleanPath isDel = createBoolean("isDel");
public final BooleanPath isHot = createBoolean("isHot");
public final StringPath keyword = createString("keyword");
public final StringPath name = createString("name");
public final NumberPath<Long> otPrice = createNumber("otPrice", Long.class);
public final NumberPath<Long> postage = createNumber("postage", Long.class);
public final StringPath price = createString("price");
public final StringPath sliderImage = createString("sliderImage");
public final NumberPath<Short> sort = createNumber("sort", Short.class);
public final BooleanPath status = createBoolean("status");
public final StringPath unitName = createString("unitName");
public final com.querydsl.sql.PrimaryKey<QProduct> primary = createPrimaryKey(id);
public QProduct(String variable) {
super(QProduct.class, forVariable(variable), "null", "product");
addMetadata();
}
public QProduct(String variable, String schema, String table) {
super(QProduct.class, forVariable(variable), schema, table);
addMetadata();
}
public QProduct(String variable, String schema) {
super(QProduct.class, forVariable(variable), schema, "product");
addMetadata();
}
public QProduct(Path<? extends QProduct> path) {
super(path.getType(), path.getMetadata(), "null", "product");
addMetadata();
}
public QProduct(PathMetadata metadata) {
super(QProduct.class, metadata, "null", "product");
addMetadata();
}
public void addMetadata() {
addMetadata(addTime, ColumnMetadata.named("add_time").withIndex(19).ofType(Types.TIMESTAMP).withSize(19).notNull());
addMetadata(cateId, ColumnMetadata.named("cate_id").withIndex(8).ofType(Types.SMALLINT).withSize(5).notNull());
addMetadata(cost, ColumnMetadata.named("cost").withIndex(11).ofType(Types.BIGINT).withSize(19).notNull());
addMetadata(desc, ColumnMetadata.named("desc").withIndex(3).ofType(Types.LONGVARCHAR).withSize(65535));
addMetadata(giveIntegral, ColumnMetadata.named("give_integral").withIndex(13).ofType(Types.BIGINT).withSize(19).notNull());
addMetadata(id, ColumnMetadata.named("id").withIndex(1).ofType(Types.VARCHAR).withSize(32).notNull());
addMetadata(image, ColumnMetadata.named("image").withIndex(6).ofType(Types.VARCHAR).withSize(256).notNull());
addMetadata(isBest, ColumnMetadata.named("is_best").withIndex(17).ofType(Types.BIT).withSize(1).notNull());
addMetadata(isDel, ColumnMetadata.named("is_del").withIndex(18).ofType(Types.BIT).withSize(1).notNull());
addMetadata(isHot, ColumnMetadata.named("is_hot").withIndex(16).ofType(Types.BIT).withSize(1).notNull());
addMetadata(keyword, ColumnMetadata.named("keyword").withIndex(4).ofType(Types.VARCHAR).withSize(256));
addMetadata(name, ColumnMetadata.named("name").withIndex(2).ofType(Types.VARCHAR).withSize(128).notNull());
addMetadata(otPrice, ColumnMetadata.named("ot_price").withIndex(10).ofType(Types.BIGINT).withSize(19).notNull());
addMetadata(postage, ColumnMetadata.named("postage").withIndex(12).ofType(Types.BIGINT).withSize(19).notNull());
addMetadata(price, ColumnMetadata.named("price").withIndex(9).ofType(Types.VARCHAR).withSize(50).notNull());
addMetadata(sliderImage, ColumnMetadata.named("slider_image").withIndex(7).ofType(Types.VARCHAR).withSize(2000).notNull());
addMetadata(sort, ColumnMetadata.named("sort").withIndex(15).ofType(Types.SMALLINT).withSize(5).notNull());
addMetadata(status, ColumnMetadata.named("status").withIndex(14).ofType(Types.BIT).withSize(1).notNull());
addMetadata(unitName, ColumnMetadata.named("unit_name").withIndex(5).ofType(Types.VARCHAR).withSize(50).notNull());
}
}
三. 使用
这里,我们主要介绍的是 querydsl-sql 的使用;要使用 queryDSL,需要做的前期准备如下:
2.1 配置
导入相关的依赖包
<dependency>
<groupId>com.querydsl</groupId>
<artifactId>querydsl-sql-spring</artifactId>
<version>4.3.1</version>
</dependency>
首先是配置具体某种数据库类型的 SQLQueryFactory。我们的例子如下:
@Bean
public SQLQueryFactory sqlQueryFactory() {
SQLTemplates mySQLTemplates = MySQLTemplates.builder().build();
com.querydsl.sql.Configuration configuration = new com.querydsl.sql.Configuration(mySQLTemplates);
configuration.setExceptionTranslator(new SpringExceptionTranslator());
//下面是注册类型
configuration.register(new EnumByNameType(Action.class));
configuration.register(new EnumByNameType(OrderStatus.class));
//这个很关键,要想使用spring事务管理器,必须要使用工具类来适配数据源
Provider provider = new SpringConnectionProvider(dataSource);
SQLQueryFactory queryFactory = new SQLQueryFactory(configuration, provider);
return queryFactory;
}
在 pom.xml 中配置插件 querydsl-maven-plugin,从而生成目标数据库的元数据;也就是上面例子中的 QProduct 对象
plugin>
<groupId>com.querydsl</groupId>
<artifactId>querydsl-maven-plugin</artifactId>
<version>4.3.1</version>
<executions>
<execution>
<goals>
<goal>export</goal>
</goals>
</execution>
</executions>
<configuration>
<jdbcDriver>com.mysql.jdbc.Driver</jdbcDriver>
<jdbcUrl>*******</jdbcUrl>
<jdbcUser>******</jdbcUser>
<jdbcPassword>******</jdbcPassword>
<packageName>*****</packageName>
<targetFolder>${project.basedir}/target/generated-sources/java</targetFolder>
//拓展数据类型
<customTypes>
<customType>com.querydsl.sql.types.JSR310LocalDateTimeType</customType>
</customTypes>
<typeMappings>
//类型映射
<typeMapping>
<table>order_cart</table>
<column>type</column>
<type>com.michael.shop.model.common.enums.CartTypeHandler</type>
</typeMapping>
</configuration>
//所依赖的包
<dependencies>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.46</version>
</dependency>
<dependency>
//之所以会加上这个依赖包,主要是因为在映射类型中的CartTypeHandler是存放在下面的依赖包中。
<groupId>com.michael</groupId>
<artifactId>shop-model</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
</dependencies>
</plugin>
2.2 使用
就跟上面的例子一样,在业务代码中注入 SQLQueryFactory 对象即可;
2.3 获取主键 ID 的值
在 mybatis 中配置 useGeneratedKeys,其实质是通过 getGeneratedKeys 拿到这次 statement 执行后生产的主键 ID。
在 QueryDSL,我们可以调用 executeWithKey 方法可以拿到数据库自动生产的主键 ID 值;
四. 总结
这里只是介绍简单的用法,虽然没有过多介绍 QueryDSL 的运作原理,只是大概阐述了其主要是通过 SQLQueryFactory 去拼装 SQL,以及结果映射,但通过上述的简单介绍,相信对其有一定的认知。后续会介绍 QueryDSL 拼装 SQL 的过程,以及结果映射原理,同时会介绍其高级配置;另外也会介绍其插件生成表对象的逻辑以及配置;
虽然 QueryDSL 只是提供了最基础的 JDBC 基本功能,拼装 SQL、结果映射。并没有提供缓存这样子的特性,我们可以自行去实现我们想要的特性功能。
在我私下写的相关的代码,基本都是基于 QueryDSL 来开发的;差不多已经算是放弃 Mybatis。
版权声明: 本文为 InfoQ 作者【邱学喆】的原创文章。
原文链接:【http://xie.infoq.cn/article/8fa5bf5e26d3570dbb18f0d3b】。
本文遵守【CC-BY 4.0】协议,转载请保留原文出处及本版权声明。
邱学喆
计算机原理的深度解读,源码分析。 2018.08.26 加入
在IT领域keep Learning。要知其然,也要知其所以然。原理的爱好,源码的阅读。输出我对原理以及源码解读的理解。个人的仓库:https://gitee.com/Michael_Chan
评论