了解 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.statusfrom product productwhere 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。我们的例子如下:
@Beanpublic 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











评论