写点什么

了解 JDBC 层之 QueryDSL

用户头像
邱学喆
关注
发布于: 4 小时前
了解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。

发布于: 4 小时前阅读数: 5
用户头像

邱学喆

关注

计算机原理的深度解读,源码分析。 2018.08.26 加入

在IT领域keep Learning。要知其然,也要知其所以然。原理的爱好,源码的阅读。输出我对原理以及源码解读的理解。个人的仓库:https://gitee.com/Michael_Chan

评论

发布
暂无评论
了解JDBC层之QueryDSL