第三章:使用QueryDSL与SpringDataJPA完成Update&Delete

我们上一章讲解了有关QueryDsl整合SpringDataJPA完成简单的单表条件查询,采用了两种模式进行查询一种是完全QueryDsl而另外一种则是整合的形式,既然单表的查询已经讲解接下来我们来看看QueryDsl与SpringDataJPA整合后的Update&Delete的多种处理模式。

本章目标

基于SpringBoot框架平台完成QueryDsl整合SpringDataJPA单表Update&Delete操作。

构建项目

我们使用idea工具创建一个空的SpringBoot项目,把上一章第二章:使用QueryDSL与SpringDataJPA实现单表普通条件查询内的配置文件复制到本章项目中(复制内容包含:application.yml,pom.xml内依赖,Bean,BaseJPA,UserJPA)复制完成后使用maven compile命令完成QueryDsl查询实体的自动创建,先来完成用户信息的更新,下面我们直接进入正题。

更新实体信息

我们采用两种方式进行更新实体信息,一种是完全采用SpringDataJPA的save方法,另外一种则是QueryDsl的update方法,下来我们先来看看SpringDataJPA如何完成更新实体信息。

使用SpringDataJPA更新实体

SpringDataJPA内置了一个save方法用于保存、更新实体内容,如果存在主键值则更新对应主键的row信息,反则是添加一条新信息,这一点跟Hibernate的saveOrUpdate方法比较相似。我们先来创建一个UserController控制器,代码如下所示:

package com.yuqiyu.querydsl.sample.chapter3.controller;

import com.querydsl.jpa.impl.JPAQueryFactory;
import com.yuqiyu.querydsl.sample.chapter3.bean.QUserBean;
import com.yuqiyu.querydsl.sample.chapter3.bean.UserBean;
import com.yuqiyu.querydsl.sample.chapter3.jpa.UserJPA;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import javax.annotation.PostConstruct;
import javax.persistence.EntityManager;

/**
 * ========================
 * Created with IntelliJ IDEA.
 * User:恒宇少年
 * Date:2017/7/4
 * Time:22:15
 * 码云:http://git.oschina.net/jnyqy
 * ========================
 */
@RestController
public class UserController
{
    @Autowired
    private UserJPA userJPA;

    //实体管理者
    @Autowired
    private EntityManager entityManager;

    //JPA查询工厂
    private JPAQueryFactory queryFactory;

    @PostConstruct
    public void initFactory()
    {
        queryFactory = new JPAQueryFactory(entityManager);
        System.out.println("init JPAQueryFactory successfully");
    }
}

上面这个控制器与第二章内的控制器基本内容没有什么差别,在bean构造函数初始化时通过EntityManager对象实例化JPAQueryFactory查询工厂实体,方便我们接下来的查询操作,QueryDsl形式是需要建立在JPAQueryFactory对象基础上构建的。SpringDataJPA方式更新实体代码如下:

    /**
     * 使用Jpa更新会员信息
     * @param userBean
     */
    @RequestMapping(value = "/updateWithJpa")
    public String updateWithJpa(UserBean userBean)
    {
        //保存会员信息相当于Hibernate内的SaveOrUpdate
        userJPA.save(userBean);
        return "SUCCESS";
    }

很简单并没有什么有难理解的地方,参数是通过界面Request方式传递的,下面我们运行下项目在上一章数据基础上尝试下更新操作。运行项目控制台输出下图1所示:

图1

看到上图1中红色标出部分我们就可以通过浏览器进行访问路径尝试更新实体信息了,下面我们来访问下地址:127.0.0.1:8080/updateWithJpa?id=6&name=change&age=23&address=SdJN&pwd=123456,界面输出内容如下图2所示:

图2

我们来看下控制台输出的日志内容,如下代码所示:

Hibernate: 
    select
        userbean0_.t_id as t_id1_0_0_,
        userbean0_.t_address as t_addres2_0_0_,
        userbean0_.t_age as t_age3_0_0_,
        userbean0_.t_name as t_name4_0_0_,
        userbean0_.t_pwd as t_pwd5_0_0_ 
    from
        t_user userbean0_ 
    where
        userbean0_.t_id=?
Hibernate: 
    update
        t_user 
    set
        t_address=?,
        t_age=?,
        t_name=?,
        t_pwd=? 
    where
        t_id=?

可以看到SpringDataJPA先去数据库查询了一边当前对象,比对发现与数据库内不一致并且存在主键值则执行了下面的Update语句,这里如果查询到的字段对应更新的内容一致时则不会执行下面的Update语句,我们再来访问下刚才的地址查看控制台输出如下代码块所示:

Hibernate: 
    select
        userbean0_.t_id as t_id1_0_0_,
        userbean0_.t_address as t_addres2_0_0_,
        userbean0_.t_age as t_age3_0_0_,
        userbean0_.t_name as t_name4_0_0_,
        userbean0_.t_pwd as t_pwd5_0_0_ 
    from
        t_user userbean0_ 
    where
        userbean0_.t_id=?

可以看到仅仅执行了查询并没有发起更新操作。

使用QueryDsl更新实体

下面我们来完全使用QueryDsl来更新实体,代码如下所示:

/**
     * 使用QueryDsl更新会员信息
     * @param userBean
     */
    @RequestMapping(value = "/updateWithQueryDsl")
    public String updateWithQueryDsl(UserBean userBean)
    {
        //querydsl查询实体
        QUserBean _Q_user = QUserBean.userBean;

        queryFactory
                .update(_Q_user)//更新对象
                //更新字段列表
                .set(_Q_user.name,userBean.getName())
                .set(_Q_user.address,userBean.getAddress())
                .set(_Q_user.age,userBean.getAge())
                .set(_Q_user.pwd,userBean.getPwd())
                //更新条件
                .where(_Q_user.id.eq(userBean.getId()))
                //执行更新
                .execute();
        return "SUCCESS";
    }

我们第一步先获取了QUserBean查询对象,并且通过JPAQueryFactory对象构建了update方法处理,而update的参数就是需要更新的查询实体,当然update方法内仅支持更新单个查询实体。

接下来我们就设置要更新的字段内容了,这里就是我们随心所欲控制了。需要更新哪些字段就设置对应字段更新的内容即可。

设置完成更新字段后需要设置更新的条件,不设置也是可以的,当然这里肯定跟原生SQL一样,不设置条件就更新表内全部的数据。

最后一步至关重要,如果不调用execute方法就不会执行更新操作。下面重启项目后访问地址:127.0.0.1:8080/updateWithQueryDsl?id=6&name=changeWithQueryDsl&age=24&address=山东济南&pwd=666666,界面输出内容如下图4所示:

图4

,我们发现这里出现了系统异常,我们来看下控制台输出的错误信息如下:

javax.persistence.TransactionRequiredException: Executing an update/delete query
    at org.hibernate.jpa.spi.AbstractQueryImpl.executeUpdate(AbstractQueryImpl.java:54) ~[hibernate-entitymanager-5.0.12.Final.jar:5.0.12.Final]
    at com.querydsl.jpa.impl.JPAUpdateClause.execute(JPAUpdateClause.java:77) ~[querydsl-jpa-4.1.4.jar:na]
    at com.yuqiyu.querydsl.sample.chapter3.controller.UserController.updateWithQueryDsl(UserController.java:76) ~[classes/:na]
.......

发现如果想执行update/delete方法时必须存在一个事务才可以,那我们修改更新方法添加事务注解@Transactional,重启项目再来访问我们之前的地址,界面输出内容如下图5所示:

图5

,界面输出了执行成功的提示,我们来看下控制台输出的SQL语句:

Hibernate: 
    update
        t_user 
    set
        t_name=?,
        t_address=?,
        t_age=?,
        t_pwd=? 
    where
        t_id=?

控制台输出的这个SQL语句就是QueryDsl根据我们配置的更新实体、更新字段、查询条件自动生成的,是不是比较灵活?

使用SpringDataJPA删除实体信息

下面我们来看看SpringDataJPA删除实体信息时该怎么处理?代码如下所示:

    /**
     * 使用Jpa删除会员信息
     * @param userBean
     */
    @RequestMapping(value = "/deleteWithJpa")
    public String deleteWithJpa(UserBean userBean)
    {
        //执行删除指定主键的值
        userJPA.delete(userBean.getId());
        return "SUCCESS";
    }

上面代码我们根据主键删除了指定的实体,下面我们重启项目后访问地址:http://127.0.0.1:8080/deleteWithJpa?id=6,控制台输出的SQL内容如下所示:

Hibernate: 
    delete 
    from
        t_user 
    where
        t_id=?

输出的SQL也是按照我们指定的主键完成了删除逻辑。

使用QueryDsl删除会员信息

在编写删除方法之前我们想到了之前使用QueryDsl更新实体时需要添加事务,当然在删除的时候也是需要的所以我们编写删除方法时要注意,删除代码如下所示:

 /**
     * 使用QueryDsl删除会员信息
     * @param userBean
     */
    @RequestMapping(value = "/deleteWithQueryDsl")
    @Transactional
    public String deleteWithQueryDsl(UserBean userBean)
    {
        //querydsl查询实体
        QUserBean _Q_user = QUserBean.userBean;

        queryFactory
                //删除对象
                .delete(_Q_user)
                //删除条件
                .where(_Q_user.id.eq(userBean.getId()))
                //执行删除
                .execute();
        return "SUCCESS";
    }

QueryDsl的删除编写与更新几乎是差不多的,只是把update方法改成了delete方法,当然where条件也可以添加多个,这个需要看个人实际业务逻辑。

删除语句编写完成后调用execute方法后QueryDsl才去处理删除逻辑。重启项目后,访问地址:127.0.0.1:8080/deleteWithQueryDsl?id=7,查看控制台输出SQL内容如下所示:

Hibernate: 
    delete 
    from
        t_user 
    where
        t_id=?

下面我们修改下删除条件,我们根据名称以及年龄大于20岁条件执行删除,修改后的方法代码如下所示:

    @RequestMapping(value = "/deleteWithQueryDsl")
    @Transactional
    public String deleteWithQueryDsl(UserBean userBean)
    {
        //querydsl查询实体
        QUserBean _Q_user = QUserBean.userBean;

        queryFactory
                //删除对象
                .delete(_Q_user)
                //删除条件
                .where(
                        _Q_user.name.eq(userBean.getName())
                        .and(_Q_user.age.gt(20))
                )
                //执行删除
                .execute();
        return "SUCCESS";
    }

重启项目后访问地址:http://127.0.0.1:8080/deleteWithQueryDsl?name=admin&age=20,控制台输出SQL如下所示:

Hibernate: 
    delete 
    from
        t_user 
    where
        t_name=? 
        and t_age>?

输出的SQL完全根据我们设置的条件来自动生成,QueryDsl内的条件可以跟原生SQL完全一样,可以完全采用SQL的思想来编写条件。

总结

以上内容就是本章的全部内容,本章主要讲解了QueryDsl是怎么操作单表Delete&Update操作以及SpringDataJPA操作单表Delete&Update。 本章代码已经上传到码云: SpringBoot配套源码地址:https://gitee.com/hengboy/spring-boot-chapter SpringCloud配套源码地址:https://gitee.com/hengboy/spring-cloud-chapter SpringBoot相关系列文章请访问:目录:SpringBoot学习目录 QueryDSL相关系列文章请访问:QueryDSL通用查询框架学习目录 SpringDataJPA相关系列文章请访问:目录:SpringDataJPA学习目录 感谢阅读!

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏Android 研究

Android系统启动——2init进程

严格上讲,Android系统实际上是运行于Linux内核之上的一系列"服务进程",并不算一个完成意义上的"操作系统";而这一系列进程是维持Android设备正常...

1112
来自专栏walterlv - 吕毅的博客

如何快速编写和调试 Emit 生成 IL 的代码

发布于 2018-04-22 12:23 更新于 2018-09...

611
来自专栏程序猿

SQL Server基础SQL脚本之分区表、分区方案

代码120行左右 本系列,几乎都是代码,记得当时写的时候用的是微软的官方实例数据库AdventureWorks_Data.mdf、AdventureWork...

3796
来自专栏C/C++基础

CMake简介及使用实例

CMake是一个跨平台的建构系统的工具,可以用简单的语句来描述所有平台的安装(编译过程)。他能够输出各种各样的构建文档makefile或者project文件,描...

982
来自专栏大内老A

Enterprise Library深入解析与灵活应用(5):创建一个简易版的批处理执行器,认识Enterprise Library典型的配置方式和对象创建方式

最近负责一个框架性项目的升级,主要是从.NET Framework 3.0建议到.NET .NET Framework 3.5,开发工具也从VS2005迁移到V...

1829
来自专栏FreeBuf

WIN10下ROP初体验

* 本文原创作者:与非门salome,本文属FreeBuf原创奖励计划,未经许可禁止转载 首先,在windows10下编写一个具有一定安全机制但又存在漏...

2139
来自专栏烙馅饼喽的技术分享

用ECMAScript4 ( ActionScript3) 实现Unity的热更新 -- 使用原型链和EventTrigger

原型链是JS的必备,作为ECMAScript4,原型链也是支持的。 特别说明,ActionScript3是支持完整的面向对象继承支持的,原型链只在某些非常特殊的...

2676
来自专栏java技术学习之道

Java日志框架:logback详解

1465
来自专栏大内老A

Enterprise Library深入解析与灵活应用(5):创建一个简易版的批处理执行器,认识Enterprise Library典型的配置方式和对象创建方式

最近负责一个框架性项目的升级,主要是从.NET Framework 3.0建议到.NET .NET Framework 3.5,开发工具也从VS2005迁移到V...

19310
来自专栏草根专栏

用VSCode开发一个基于asp.net core 2.0/sql server linux(docker)/ng5/bs4的项目(2)

为Domain Model添加约束 前一部分, 我们已经把数据库创建出来了. 那么我们先看看这个数据库. 可以在项目里面建立一个database.sql, 并且...

2935

扫码关注云+社区