mybatis学习(三)

声明:该内容来源于动力节点,本人在学习mybatis过程中参考该内容,并自己做了部分笔记,但个人觉得本人做的笔记不如动力节点做的好,故使用动力节点的笔记作为后续mybatis的复习。

六、在WEB中应用MyBatis(使用MVC架构模式)

目标:

  • 掌握mybatis在web应用中怎么用
  • mybatis三大对象的作用域和生命周期
  • ThreadLocal原理及使用
  • 巩固MVC架构模式
  • 为学习MyBatis的接口代理机制做准备

实现功能:

  • 银行账户转账

使用技术:

  • HTML + Servlet + MyBatis

WEB应用的名称:

  • bank

6.1 需求描述

9C2CCE44-E51B-4b1a-AD3D-176294F3B463.png

6.2 数据库表的设计和准备数据

68216368-5BD6-4e0d-A567-5E725A349D0F.png
EBB28A3F-32BB-431c-9C2C-03A4EB5C8ADF.png

6.3 实现步骤

第一步:环境搭建

  • IDEA中创建Maven WEB应用(mybatis-004-web

B228544B-4646-41fc-BFA1-39B6E8464405.png

  • IDEA配置Tomcat,这里Tomcat使用10+版本。并部署应用到tomcat。

38908B1E-AC59-4ecf-B972-7651AA58023E.png
9EA5CF36-E282-41d4-B544-1533AD25BB33.png

  • 默认创建的maven web应用没有java和resources目录,包括两种解决方案
    • 第一种:自己手动加上。

5A0EC4A7-C5EB-4fcb-B74C-2A9B3A147E5B.png

  • 第二种:修改maven-archetype-webapp-1.4.jar中的配置文件

508D0561-6CEF-4af4-A243-0CF29B2D748B.png
3A2D1F51-98DE-4416-B196-2535C5D26E55.png
7F5D0F8B-EA3B-4d79-B156-02C0D11BA2E9.png

  • web.xml文件的版本较低,可以从tomcat10的样例文件中复制,然后修改
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="https://jakarta.ee/xml/ns/jakartaee"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="https://jakarta.ee/xml/ns/jakartaeehttps://jakarta.ee/xml/ns/jakartaee/web-app_5_0.xsd"version="5.0"metadata-complete="true"></web-app>
  • 删除index.jsp文件,因为我们这个项目不使用JSP。只使用html。
  • 确定pom.xml文件中的打包方式是war包。
  • 引入相关依赖
    • 编译器版本修改为17
    • 引入的依赖包括:mybatis,mysql驱动,junit,logback,servlet。
<?xml version="1.0" encoding="UTF-8"?><project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"><modelVersion>4.0.0</modelVersion><groupId>com.powernode</groupId><artifactId>mybatis-004-web</artifactId><version>1.0-SNAPSHOT</version><packaging>war</packaging><name>mybatis-004-web</name><url>http://localhost:8080/bank</url><properties><project.build.sourceEncoding>UTF-8</project.build.sourceEncoding><maven.compiler.source>17</maven.compiler.source><maven.compiler.target>17</maven.compiler.target></properties><dependencies><!--mybatis依赖--><dependency><groupId>org.mybatis</groupId><artifactId>mybatis</artifactId><version>3.5.10</version></dependency><!--mysql驱动依赖--><dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId><version>8.0.30</version></dependency><!--junit依赖--><dependency><groupId>junit</groupId><artifactId>junit</artifactId><version>4.13.2</version><scope>test</scope></dependency><!--logback依赖--><dependency><groupId>ch.qos.logback</groupId><artifactId>logback-classic</artifactId><version>1.2.11</version></dependency><!--servlet依赖--><dependency><groupId>jakarta.servlet</groupId><artifactId>jakarta.servlet-api</artifactId><version>5.0.0</version><scope>provided</scope></dependency></dependencies><build><finalName>mybatis-004-web</finalName><pluginManagement><plugins><plugin><artifactId>maven-clean-plugin</artifactId><version>3.1.0</version></plugin><plugin><artifactId>maven-resources-plugin</artifactId><version>3.0.2</version></plugin><plugin><artifactId>maven-compiler-plugin</artifactId><version>3.8.0</version></plugin><plugin><artifactId>maven-surefire-plugin</artifactId><version>2.22.1</version></plugin><plugin><artifactId>maven-war-plugin</artifactId><version>3.2.2</version></plugin><plugin><artifactId>maven-install-plugin</artifactId><version>2.5.2</version></plugin><plugin><artifactId>maven-deploy-plugin</artifactId><version>2.8.2</version></plugin></plugins></pluginManagement></build>
</project>
  • 引入相关配置文件,放到resources目录下(全部放到类的根路径下)
    • mybatis-config.xml
    • AccountMapper.xml
    • logback.xml
    • jdbc.properties
jdbc.driver=com.mysql.cj.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/powernode
jdbc.username=root
jdbc.password=root
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configurationPUBLIC "-//mybatis.org//DTD Config 3.0//EN""http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration><properties resource="jdbc.properties"/><environments default="dev"><environment id="dev"><transactionManager type="JDBC"/><dataSource type="POOLED"><property name="driver" value="${jdbc.driver}"/><property name="url" value="${jdbc.url}"/><property name="username" value="${jdbc.username}"/><property name="password" value="${jdbc.password}"/></dataSource></environment></environments><mappers><!--一定要注意这里的路径哦!!!--><mapper resource="AccountMapper.xml"/></mappers>
</configuration>
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapperPUBLIC "-//mybatis.org//DTD Mapper 3.0//EN""http://mybatis.org/dtd/mybatis-3-mapper.dtd"><mapper namespace="account"></mapper>

第二步:前端页面index.html

<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><title>银行账户转账</title>
</head>
<body>
<!--/bank是应用的根,部署web应用到tomcat的时候一定要注意这个名字-->
<form action="/bank/transfer" method="post">转出账户:<input type="text" name="fromActno"/><br>转入账户:<input type="text" name="toActno"/><br>转账金额:<input type="text" name="money"/><br><input type="submit" value="转账"/>
</form>
</body>
</html>

第三步:创建pojo包、service包、dao包、web包、utils包

  • com.powernode.bank.pojo
  • com.powernode.bank.service
  • com.powernode.bank.service.impl
  • com.powernode.bank.dao
  • com.powernode.bank.dao.impl
  • com.powernode.bank.web.controller
  • com.powernode.bank.exception
  • com.powernode.bank.utils:将之前编写的SqlSessionUtil工具类拷贝到该包下。

第四步:定义pojo类:Account

package com.powernode.bank.pojo;/*** 银行账户类* @author 老杜* @version 1.0* @since 1.0*/
public class Account {private Long id;private String actno;private Double balance;@Overridepublic String toString() {return "Account{" +"id=" + id +", actno='" + actno + '\'' +", balance=" + balance +'}';}public Account() {}public Account(Long id, String actno, Double balance) {this.id = id;this.actno = actno;this.balance = balance;}public Long getId() {return id;}public void setId(Long id) {this.id = id;}public String getActno() {return actno;}public void setActno(String actno) {this.actno = actno;}public Double getBalance() {return balance;}public void setBalance(Double balance) {this.balance = balance;}
}

第五步:编写AccountDao接口,以及AccountDaoImpl实现类

分析dao中至少要提供几个方法,才能完成转账:

  • 转账前需要查询余额是否充足:selectByActno
  • 转账时要更新账户:update
package com.powernode.bank.dao;import com.powernode.bank.pojo.Account;/*** 账户数据访问对象* @author 老杜* @version 1.0* @since 1.0*/
public interface AccountDao {/*** 根据账号获取账户信息* @param actno 账号* @return 账户信息*/Account selectByActno(String actno);/*** 更新账户信息* @param act 账户信息* @return 1表示更新成功,其他值表示失败*/int update(Account act);
}
package com.powernode.bank.dao.impl;import com.powernode.bank.dao.AccountDao;
import com.powernode.bank.pojo.Account;
import com.powernode.bank.utils.SqlSessionUtil;
import org.apache.ibatis.session.SqlSession;public class AccountDaoImpl implements AccountDao {@Overridepublic Account selectByActno(String actno) {SqlSession sqlSession = SqlSessionUtil.openSession();Account act = (Account)sqlSession.selectOne("selectByActno", actno);sqlSession.close();return act;}@Overridepublic int update(Account act) {SqlSession sqlSession = SqlSessionUtil.openSession();int count = sqlSession.update("update", act);sqlSession.commit();sqlSession.close();return count;}
}

第六步:AccountDaoImpl中编写了mybatis代码,需要编写SQL映射文件了

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapperPUBLIC "-//mybatis.org//DTD Mapper 3.0//EN""http://mybatis.org/dtd/mybatis-3-mapper.dtd"><mapper namespace="account"><select id="selectByActno" resultType="com.powernode.bank.pojo.Account">select * from t_act where actno = #{actno}</select><update id="update">update t_act set balance = #{balance} where actno = #{actno}</update>
</mapper>

第七步:编写AccountService接口以及AccountServiceImpl

package com.powernode.bank.exception;/*** 余额不足异常* @author 老杜* @version 1.0* @since 1.0*/
public class MoneyNotEnoughException extends Exception{public MoneyNotEnoughException(){}public MoneyNotEnoughException(String msg){ super(msg); }
}
package com.powernode.bank.exception;/*** 应用异常* @author 老杜* @version 1.0* @since 1.0*/
public class AppException extends Exception{public AppException(){}public AppException(String msg){ super(msg); }
}
package com.powernode.bank.service;import com.powernode.bank.exception.AppException;
import com.powernode.bank.exception.MoneyNotEnoughException;/*** 账户业务类。* @author 老杜* @version 1.0* @since 1.0*/
public interface AccountService {/*** 银行账户转正* @param fromActno 转出账户* @param toActno 转入账户* @param money 转账金额* @throws MoneyNotEnoughException 余额不足异常* @throws AppException App发生异常*/void transfer(String fromActno, String toActno, double money) throws MoneyNotEnoughException, AppException;
}
package com.powernode.bank.service.impl;import com.powernode.bank.dao.AccountDao;
import com.powernode.bank.dao.impl.AccountDaoImpl;
import com.powernode.bank.exception.AppException;
import com.powernode.bank.exception.MoneyNotEnoughException;
import com.powernode.bank.pojo.Account;
import com.powernode.bank.service.AccountService;public class AccountServiceImpl implements AccountService {private AccountDao accountDao = new AccountDaoImpl();@Overridepublic void transfer(String fromActno, String toActno, double money) throws MoneyNotEnoughException, AppException {// 查询转出账户的余额Account fromAct = accountDao.selectByActno(fromActno);if (fromAct.getBalance() < money) {throw new MoneyNotEnoughException("对不起,您的余额不足。");}try {// 程序如果执行到这里说明余额充足// 修改账户余额Account toAct = accountDao.selectByActno(toActno);fromAct.setBalance(fromAct.getBalance() - money);toAct.setBalance(toAct.getBalance() + money);// 更新数据库accountDao.update(fromAct);accountDao.update(toAct);} catch (Exception e) {throw new AppException("转账失败,未知原因!");}}
}

第八步:编写AccountController

package com.powernode.bank.web.controller;import com.powernode.bank.exception.AppException;
import com.powernode.bank.exception.MoneyNotEnoughException;
import com.powernode.bank.service.AccountService;
import com.powernode.bank.service.impl.AccountServiceImpl;import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;/*** 账户控制器* @author 老杜* @version 1.0* @since 1.0*/
@WebServlet("/transfer")
public class AccountController extends HttpServlet {private AccountService accountService = new AccountServiceImpl();@Overrideprotected void doPost(HttpServletRequest request, HttpServletResponse response)throws ServletException, IOException {// 获取响应流response.setContentType("text/html;charset=UTF-8");PrintWriter out = response.getWriter();// 获取账户信息String fromActno = request.getParameter("fromActno");String toActno = request.getParameter("toActno");double money = Integer.parseInt(request.getParameter("money"));// 调用业务方法完成转账try {accountService.transfer(fromActno, toActno, money);out.print("<h1>转账成功!!!</h1>");} catch (MoneyNotEnoughException e) {out.print(e.getMessage());} catch (AppException e) {out.print(e.getMessage());}}
}

启动服务器,打开浏览器,输入地址:http://localhost:8080/bank,测试:
EDF0C904-B6CB-44bf-9F9F-C69C81AE46A2.png
A3F83ADE-1028-49ff-9522-8595D857A421.png
5364CDC3-DF1A-4380-8A11-DBA050A17A77.png

6.4 MyBatis对象作用域以及事务问题

MyBatis核心对象的作用域

SqlSessionFactoryBuilder

这个类可以被实例化、使用和丢弃,一旦创建了 SqlSessionFactory,就不再需要它了。 因此 SqlSessionFactoryBuilder 实例的最佳作用域是方法作用域(也就是局部方法变量)。 你可以重用 SqlSessionFactoryBuilder 来创建多个 SqlSessionFactory 实例,但最好还是不要一直保留着它,以保证所有的 XML 解析资源可以被释放给更重要的事情。

SqlSessionFactory

SqlSessionFactory 一旦被创建就应该在应用的运行期间一直存在,没有任何理由丢弃它或重新创建另一个实例。 使用 SqlSessionFactory 的最佳实践是在应用运行期间不要重复创建多次,多次重建 SqlSessionFactory 被视为一种代码“坏习惯”。因此 SqlSessionFactory 的最佳作用域是应用作用域。 有很多方法可以做到,最简单的就是使用单例模式或者静态单例模式。

SqlSession

每个线程都应该有它自己的 SqlSession 实例。SqlSession 的实例不是线程安全的,因此是不能被共享的,所以它的最佳的作用域是请求或方法作用域。 绝对不能将 SqlSession 实例的引用放在一个类的静态域,甚至一个类的实例变量也不行。 也绝不能将 SqlSession 实例的引用放在任何类型的托管作用域中,比如 Servlet 框架中的 HttpSession。 如果你现在正在使用一种 Web 框架,考虑将 SqlSession 放在一个和 HTTP 请求相似的作用域中。 换句话说,每次收到 HTTP 请求,就可以打开一个 SqlSession,返回一个响应后,就关闭它。 这个关闭操作很重要,为了确保每次都能执行关闭操作,你应该把这个关闭操作放到 finally 块中。 下面的示例就是一个确保 SqlSession 关闭的标准模式:

try (SqlSession session = sqlSessionFactory.openSession()) {// 你的应用逻辑代码
}

事务问题

在之前的转账业务中,更新了两个账户,我们需要保证它们的同时成功或同时失败,这个时候就需要使用事务机制,在transfer方法开始执行时开启事务,直到两个更新都成功之后,再提交事务,我们尝试将transfer方法进行如下修改:

package com.powernode.bank.service.impl;import com.powernode.bank.dao.AccountDao;
import com.powernode.bank.dao.impl.AccountDaoImpl;
import com.powernode.bank.exception.AppException;
import com.powernode.bank.exception.MoneyNotEnoughException;
import com.powernode.bank.pojo.Account;
import com.powernode.bank.service.AccountService;
import com.powernode.bank.utils.SqlSessionUtil;
import org.apache.ibatis.session.SqlSession;public class AccountServiceImpl implements AccountService {private AccountDao accountDao = new AccountDaoImpl();@Overridepublic void transfer(String fromActno, String toActno, double money) throws MoneyNotEnoughException, AppException {// 查询转出账户的余额Account fromAct = accountDao.selectByActno(fromActno);if (fromAct.getBalance() < money) {throw new MoneyNotEnoughException("对不起,您的余额不足。");}try {// 程序如果执行到这里说明余额充足// 修改账户余额Account toAct = accountDao.selectByActno(toActno);fromAct.setBalance(fromAct.getBalance() - money);toAct.setBalance(toAct.getBalance() + money);// 更新数据库(添加事务)SqlSession sqlSession = SqlSessionUtil.openSession();accountDao.update(fromAct);// 模拟异常String s = null;s.toString();accountDao.update(toAct);sqlSession.commit();sqlSession.close();} catch (Exception e) {throw new AppException("转账失败,未知原因!");}}
}

运行前注意看数据库表中当前的数据:
5364CDC3-DF1A-4380-8A11-DBA050A17A77.png
执行程序:
44F99896-3045-4700-BF02-33B0E2CEE73E.png
740BDFDF-1EA7-4134-8867-DEA8AD869800.png
再次查看数据库表中的数据:
A737B160-044F-4511-A4E5-24427A4ADABB.png
傻眼了吧!!!事务出问题了,转账失败了,钱仍然是少了1万。这是什么原因呢?主要是因为service和dao中使用的SqlSession对象不是同一个。
怎么办?为了保证service和dao中使用的SqlSession对象是同一个,可以将SqlSession对象存放到ThreadLocal当中。修改SqlSessionUtil工具类:

package com.powernode.bank.utils;import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;/*** MyBatis工具类** @author 老杜* @version 1.0* @since 1.0*/
public class SqlSessionUtil {private static SqlSessionFactory sqlSessionFactory;/*** 类加载时初始化sqlSessionFactory对象*/static {try {SqlSessionFactoryBuilder sqlSessionFactoryBuilder = new SqlSessionFactoryBuilder();sqlSessionFactory = sqlSessionFactoryBuilder.build(Resources.getResourceAsStream("mybatis-config.xml"));} catch (Exception e) {e.printStackTrace();}}private static ThreadLocal<SqlSession> local = new ThreadLocal<>();/*** 每调用一次openSession()可获取一个新的会话,该会话支持自动提交。** @return 新的会话对象*/public static SqlSession openSession() {SqlSession sqlSession = local.get();if (sqlSession == null) {sqlSession = sqlSessionFactory.openSession();local.set(sqlSession);}return sqlSession;}/*** 关闭SqlSession对象* @param sqlSession*/public static void close(SqlSession sqlSession){if (sqlSession != null) {sqlSession.close();}local.remove();}
}

修改dao中的方法:AccountDaoImpl中所有方法中的提交commit和关闭close代码全部删除。

package com.powernode.bank.dao.impl;import com.powernode.bank.dao.AccountDao;
import com.powernode.bank.pojo.Account;
import com.powernode.bank.utils.SqlSessionUtil;
import org.apache.ibatis.session.SqlSession;public class AccountDaoImpl implements AccountDao {@Overridepublic Account selectByActno(String actno) {SqlSession sqlSession = SqlSessionUtil.openSession();Account act = (Account)sqlSession.selectOne("account.selectByActno", actno);return act;}@Overridepublic int update(Account act) {SqlSession sqlSession = SqlSessionUtil.openSession();int count = sqlSession.update("account.update", act);return count;}
}

修改service中的方法:

package com.powernode.bank.service.impl;import com.powernode.bank.dao.AccountDao;
import com.powernode.bank.dao.impl.AccountDaoImpl;
import com.powernode.bank.exception.AppException;
import com.powernode.bank.exception.MoneyNotEnoughException;
import com.powernode.bank.pojo.Account;
import com.powernode.bank.service.AccountService;
import com.powernode.bank.utils.SqlSessionUtil;
import org.apache.ibatis.session.SqlSession;public class AccountServiceImpl implements AccountService {private AccountDao accountDao = new AccountDaoImpl();@Overridepublic void transfer(String fromActno, String toActno, double money) throws MoneyNotEnoughException, AppException {// 查询转出账户的余额Account fromAct = accountDao.selectByActno(fromActno);if (fromAct.getBalance() < money) {throw new MoneyNotEnoughException("对不起,您的余额不足。");}try {// 程序如果执行到这里说明余额充足// 修改账户余额Account toAct = accountDao.selectByActno(toActno);fromAct.setBalance(fromAct.getBalance() - money);toAct.setBalance(toAct.getBalance() + money);// 更新数据库(添加事务)SqlSession sqlSession = SqlSessionUtil.openSession();accountDao.update(fromAct);// 模拟异常String s = null;s.toString();accountDao.update(toAct);sqlSession.commit();SqlSessionUtil.close(sqlSession);  // 只修改了这一行代码。} catch (Exception e) {throw new AppException("转账失败,未知原因!");}}
}

当前数据库表中的数据:
A737B160-044F-4511-A4E5-24427A4ADABB.png
再次运行程序:
44F99896-3045-4700-BF02-33B0E2CEE73E.png
740BDFDF-1EA7-4134-8867-DEA8AD869800.png
查看数据库表:没有问题。
A737B160-044F-4511-A4E5-24427A4ADABB.png
再测试转账成功:
44F99896-3045-4700-BF02-33B0E2CEE73E.png
A3F83ADE-1028-49ff-9522-8595D857A421.png
C8F84292-5B90-4014-A348-A800DD401C86.png
如果余额不足呢:
7E69C82A-F50C-4a8d-B7FF-579C3AD5750C.png
F5CFB1B3-72F8-4960-B740-1AD2A076C499.png
账户的余额依然正常:
C8F84292-5B90-4014-A348-A800DD401C86.png

6.5 分析当前程序存在的问题

我们来看一下DaoImpl的代码

package com.powernode.bank.dao.impl;import com.powernode.bank.dao.AccountDao;
import com.powernode.bank.pojo.Account;
import com.powernode.bank.utils.SqlSessionUtil;
import org.apache.ibatis.session.SqlSession;public class AccountDaoImpl implements AccountDao {@Overridepublic Account selectByActno(String actno) {SqlSession sqlSession = SqlSessionUtil.openSession();Account act = (Account)sqlSession.selectOne("account.selectByActno", actno);return act;}@Overridepublic int update(Account act) {SqlSession sqlSession = SqlSessionUtil.openSession();int count = sqlSession.update("account.update", act);return count;}
}

我们不难发现,这个dao实现类中的方法代码很固定,基本上就是一行代码,通过SqlSession对象调用insert、delete、update、select等方法,这个类中的方法没有任何业务逻辑,既然是这样,这个类我们能不能动态的生成,以后可以不写这个类吗?答案:可以。

七、使用javassist生成类

来自百度百科:

Javassist是一个开源的分析、编辑和创建Java字节码的类库。是由东京工业大学的数学和计算机科学系的 Shigeru Chiba (千叶 滋)所创建的。它已加入了开放源代码JBoss 应用服务器项目,通过使用Javassist对字节码操作为JBoss实现动态"AOP"框架。

7.1 Javassist的使用

我们要使用javassist,首先要引入它的依赖

<dependency><groupId>org.javassist</groupId><artifactId>javassist</artifactId><version>3.29.1-GA</version>
</dependency>

样例代码:

package com.powernode.javassist;import javassist.ClassPool;
import javassist.CtClass;
import javassist.CtMethod;
import javassist.Modifier;import java.lang.reflect.Method;public class JavassistTest {public static void main(String[] args) throws Exception {// 获取类池ClassPool pool = ClassPool.getDefault();// 创建类CtClass ctClass = pool.makeClass("com.powernode.javassist.Test");// 创建方法// 1.返回值类型 2.方法名 3.形式参数列表 4.所属类CtMethod ctMethod = new CtMethod(CtClass.voidType, "execute", new CtClass[]{}, ctClass);// 设置方法的修饰符列表ctMethod.setModifiers(Modifier.PUBLIC);// 设置方法体ctMethod.setBody("{System.out.println(\"hello world\");}");// 给类添加方法ctClass.addMethod(ctMethod);// 调用方法Class<?> aClass = ctClass.toClass();Object o = aClass.newInstance();Method method = aClass.getDeclaredMethod("execute");method.invoke(o);}
}

运行要注意:加两个参数,要不然会有异常。

  • –add-opens java.base/java.lang=ALL-UNNAMED
  • –add-opens java.base/sun.net.util=ALL-UNNAMED

DCBBD517-3C79-456f-817F-DB39E85661D3.png
运行结果:
B602078D-41E9-4d86-8735-36291B78380F.png

7.2 使用Javassist生成DaoImpl类

使用Javassist动态生成DaoImpl类

package com.powernode.bank.utils;import org.apache.ibatis.javassist.CannotCompileException;
import org.apache.ibatis.javassist.ClassPool;
import org.apache.ibatis.javassist.CtClass;
import org.apache.ibatis.javassist.CtMethod;
import org.apache.ibatis.session.SqlSession;import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.Arrays;/*** 使用javassist库动态生成dao接口的实现类** @author 老杜* @version 1.0* @since 1.0*/
public class GenerateDaoByJavassist {/*** 根据dao接口生成dao接口的代理对象** @param sqlSession   sql会话* @param daoInterface dao接口* @return dao接口代理对象*/public static Object getMapper(SqlSession sqlSession, Class daoInterface) {ClassPool pool = ClassPool.getDefault();// 生成代理类CtClass ctClass = pool.makeClass(daoInterface.getPackageName() + ".impl." + daoInterface.getSimpleName() + "Impl");// 接口CtClass ctInterface = pool.makeClass(daoInterface.getName());// 代理类实现接口ctClass.addInterface(ctInterface);// 获取所有的方法Method[] methods = daoInterface.getDeclaredMethods();Arrays.stream(methods).forEach(method -> {// 拼接方法的签名StringBuilder methodStr = new StringBuilder();String returnTypeName = method.getReturnType().getName();methodStr.append(returnTypeName);methodStr.append(" ");String methodName = method.getName();methodStr.append(methodName);methodStr.append("(");Class<?>[] parameterTypes = method.getParameterTypes();for (int i = 0; i < parameterTypes.length; i++) {methodStr.append(parameterTypes[i].getName());methodStr.append(" arg");methodStr.append(i);if (i != parameterTypes.length - 1) {methodStr.append(",");}}methodStr.append("){");// 方法体当中的代码怎么写?// 获取sqlId(这里非常重要:因为这行代码导致以后namespace必须是接口的全限定接口名,sqlId必须是接口中方法的方法名。)String sqlId = daoInterface.getName() + "." + methodName;// 获取SqlCommondTypeString sqlCommondTypeName = sqlSession.getConfiguration().getMappedStatement(sqlId).getSqlCommandType().name();if ("SELECT".equals(sqlCommondTypeName)) {methodStr.append("org.apache.ibatis.session.SqlSession sqlSession = com.powernode.bank.utils.SqlSessionUtil.openSession();");methodStr.append("Object obj = sqlSession.selectOne(\"" + sqlId + "\", arg0);");methodStr.append("return (" + returnTypeName + ")obj;");} else if ("UPDATE".equals(sqlCommondTypeName)) {methodStr.append("org.apache.ibatis.session.SqlSession sqlSession = com.powernode.bank.utils.SqlSessionUtil.openSession();");methodStr.append("int count = sqlSession.update(\"" + sqlId + "\", arg0);");methodStr.append("return count;");}methodStr.append("}");System.out.println(methodStr);try {// 创建CtMethod对象CtMethod ctMethod = CtMethod.make(methodStr.toString(), ctClass);ctMethod.setModifiers(Modifier.PUBLIC);// 将方法添加到类ctClass.addMethod(ctMethod);} catch (CannotCompileException e) {throw new RuntimeException(e);}});try {// 创建代理对象Class<?> aClass = ctClass.toClass();Constructor<?> defaultCon = aClass.getDeclaredConstructor();Object o = defaultCon.newInstance();return o;} catch (Exception e) {throw new RuntimeException(e);}}
}

修改AccountMapper.xml文件:namespace必须是dao接口的全限定名称,id必须是dao接口中的方法名:

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapperPUBLIC "-//mybatis.org//DTD Mapper 3.0//EN""http://mybatis.org/dtd/mybatis-3-mapper.dtd"><mapper namespace="com.powernode.bank.dao.AccountDao"><select id="selectByActno" resultType="com.powernode.bank.pojo.Account">select * from t_act where actno = #{actno}</select><update id="update">update t_act set balance = #{balance} where actno = #{actno}</update>
</mapper>

修改service类中获取dao对象的代码:
0A0D47FC-3EA0-4d39-A715-836AF32DBCAB.png
启动服务器:启动过程中显示,tomcat服务器自动添加了以下的两个运行参数。所以不需要再单独配置。
0A086C8D-1CEF-4413-ACB7-060D1B0687F8.png
测试前数据:
2B1E7468-040C-4cf1-BD41-C6D64BFD3039.png
打开浏览器测试:
032AAA55-80B6-40dd-8666-C6C2045BB788.png
A9E86E57-0344-43ea-B79C-4C2143151637.png
4AE10366-C84B-4f56-A474-2ECDE3D974DF.png

八、MyBatis中接口代理机制及使用

好消息!!!其实以上所讲内容mybatis内部已经实现了。直接调用以下代码即可获取dao接口的代理类:

AccountDao accountDao = (AccountDao)sqlSession.getMapper(AccountDao.class);

使用以上代码的前提是:AccountMapper.xml文件中的namespace必须和dao接口的全限定名称一致,id必须和dao接口中方法名一致。
将service中获取dao对象的代码再次修改,如下:
C9B4A2C2-99DD-456b-BE6D-A4F4AD84C876.png
测试前数据:
2F131001-B06E-4db9-88D0-10D9F8879D56.png
测试后数据:
F6242FE9-51E9-47e5-BA47-5B5049DA374C.png

九、MyBatis小技巧

9.1 #{}和${}

#{}:先编译sql语句,再给占位符传值,底层是PreparedStatement实现。可以防止sql注入,比较常用。
${}:先进行sql语句拼接,然后再编译sql语句,底层是Statement实现。存在sql注入现象。只有在需要进行sql语句关键字拼接的情况下才会用到。
需求:根据car_type查询汽车
模块名:mybatis-005-antic

使用#{}

依赖

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"><modelVersion>4.0.0</modelVersion><groupId>com.powernode</groupId><artifactId>mybatis-005-antic</artifactId><version>1.0-SNAPSHOT</version><packaging>jar</packaging><dependencies><!--mybatis依赖--><dependency><groupId>org.mybatis</groupId><artifactId>mybatis</artifactId><version>3.5.10</version></dependency><!--mysql驱动依赖--><dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId><version>8.0.30</version></dependency><!--junit依赖--><dependency><groupId>junit</groupId><artifactId>junit</artifactId><version>4.13.2</version><scope>test</scope></dependency><!--logback依赖--><dependency><groupId>ch.qos.logback</groupId><artifactId>logback-classic</artifactId><version>1.2.11</version></dependency></dependencies><properties><maven.compiler.source>17</maven.compiler.source><maven.compiler.target>17</maven.compiler.target></properties></project>

jdbc.properties放在类的根路径下

jdbc.driver=com.mysql.cj.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/powernode
jdbc.username=root
jdbc.password=root

logback.xml,可以拷贝之前的,放到类的根路径下
utils

package com.powernode.mybatis.utils;import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;/*** MyBatis工具类** @author 老杜* @version 1.0* @since 1.0*/
public class SqlSessionUtil {private static SqlSessionFactory sqlSessionFactory;/*** 类加载时初始化sqlSessionFactory对象*/static {try {SqlSessionFactoryBuilder sqlSessionFactoryBuilder = new SqlSessionFactoryBuilder();sqlSessionFactory = sqlSessionFactoryBuilder.build(Resources.getResourceAsStream("mybatis-config.xml"));} catch (Exception e) {e.printStackTrace();}}private static ThreadLocal<SqlSession> local = new ThreadLocal<>();/*** 每调用一次openSession()可获取一个新的会话,该会话支持自动提交。** @return 新的会话对象*/public static SqlSession openSession() {SqlSession sqlSession = local.get();if (sqlSession == null) {sqlSession = sqlSessionFactory.openSession();local.set(sqlSession);}return sqlSession;}/*** 关闭SqlSession对象* @param sqlSession*/public static void close(SqlSession sqlSession){if (sqlSession != null) {sqlSession.close();}local.remove();}
}

pojo

package com.powernode.mybatis.pojo;
/*** 普通实体类:汽车* @author 老杜* @version 1.0* @since 1.0*/
public class Car {private Long id;private String carNum;private String brand;private Double guidePrice;private String produceTime;private String carType;// 构造方法// set get方法// toString方法
}

mapper接口

package com.powernode.mybatis.mapper;import com.powernode.mybatis.pojo.Car;import java.util.List;/*** Car的sql映射对象* @author 老杜* @version 1.0* @since 1.0*/
public interface CarMapper {/*** 根据car_num获取Car* @param carType* @return*/List<Car> selectByCarType(String carType);}

mybatis-config.xml,放在类的根路径下

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configurationPUBLIC "-//mybatis.org//DTD Config 3.0//EN""http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration><properties resource="jdbc.properties"/><environments default="dev"><environment id="dev"><transactionManager type="JDBC"/><dataSource type="POOLED"><property name="driver" value="${jdbc.driver}"/><property name="url" value="${jdbc.url}"/><property name="username" value="${jdbc.username}"/><property name="password" value="${jdbc.password}"/></dataSource></environment></environments><mappers><mapper resource="CarMapper.xml"/></mappers>
</configuration>

CarMapper.xml,放在类的根路径下:注意namespace必须和接口名一致。id必须和接口中方法名一致

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapperPUBLIC "-//mybatis.org//DTD Mapper 3.0//EN""http://mybatis.org/dtd/mybatis-3-mapper.dtd"><mapper namespace="com.powernode.mybatis.mapper.CarMapper"><select id="selectByCarType" resultType="com.powernode.mybatis.pojo.Car">selectid,car_num as carNum,brand,guide_price as guidePrice,produce_time as produceTime,car_type as carTypefromt_carwherecar_type = #{carType}</select>
</mapper>

测试程序

package com.powernode.mybatis.test;import com.powernode.mybatis.mapper.CarMapper;
import com.powernode.mybatis.pojo.Car;
import com.powernode.mybatis.utils.SqlSessionUtil;
import org.junit.Test;import java.util.List;/*** CarMapper测试类* @author 老杜* @version 1.0* @since 1.0*/
public class CarMapperTest {@Testpublic void testSelectByCarType(){CarMapper mapper = (CarMapper) SqlSessionUtil.openSession().getMapper(CarMapper.class);List<Car> cars = mapper.selectByCarType("燃油车");cars.forEach(car -> System.out.println(car));}
}

执行结果:
5EDAF14B-4BC2-4ad1-A204-9AF8CAF39557.png
通过执行可以清楚的看到,sql语句中是带有 ? 的,这个 ? 就是大家在JDBC中所学的占位符,专门用来接收值的。
把“燃油车”以String类型的值,传递给 ?
这就是 #{},它会先进行sql语句的预编译,然后再给占位符传值

使用${}

同样的需求,我们使用${}来完成
CarMapper.xml文件修改如下:

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapperPUBLIC "-//mybatis.org//DTD Mapper 3.0//EN""http://mybatis.org/dtd/mybatis-3-mapper.dtd"><mapper namespace="com.powernode.mybatis.mapper.CarMapper"><select id="selectByCarType" resultType="com.powernode.mybatis.pojo.Car">selectid,car_num as carNum,brand,guide_price as guidePrice,produce_time as produceTime,car_type as carTypefromt_carwhere<!--car_type = #{carType}-->car_type = ${carType}</select>
</mapper>

再次运行测试程序:
F8DB6119-8032-47f2-941F-ECCD4B9B5ABF.png
出现异常了,这是为什么呢?看看生成的sql语句:
A978AA37-AED0-4576-8BA9-9F63ED9ECF81.png
很显然,${} 是先进行sql语句的拼接,然后再编译,出现语法错误是正常的,因为 燃油车 是一个字符串,在sql语句中应该添加单引号
修改:

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapperPUBLIC "-//mybatis.org//DTD Mapper 3.0//EN""http://mybatis.org/dtd/mybatis-3-mapper.dtd"><mapper namespace="com.powernode.mybatis.mapper.CarMapper"><select id="selectByCarType" resultType="com.powernode.mybatis.pojo.Car">selectid,car_num as carNum,brand,guide_price as guidePrice,produce_time as produceTime,car_type as carTypefromt_carwhere<!--car_type = #{carType}--><!--car_type = ${carType}-->car_type = '${carType}'</select>
</mapper>

再执行测试程序:
E38C761A-CCCF-4764-867E-1A361BC23CEB.png
通过以上测试,可以看出,对于以上这种需求来说,还是建议使用 #{} 的方式。
原则:能用 #{} 就不用 ${}

什么情况下必须使用${}

当需要进行sql语句关键字拼接的时候。必须使用${}
需求:通过向sql语句中注入asc或desc关键字,来完成数据的升序或降序排列。

  • 先使用#{}尝试:

CarMapper接口:

/*** 查询所有的Car* @param ascOrDesc asc或desc* @return*/
List<Car> selectAll(String ascOrDesc);

CarMapper.xml文件:

<select id="selectAll" resultType="com.powernode.mybatis.pojo.Car">selectid,car_num as carNum,brand,guide_price as guidePrice,produce_time as produceTime,car_type as carTypefromt_carorder by carNum #{key}
</select>

测试程序

@Test
public void testSelectAll(){CarMapper mapper = (CarMapper) SqlSessionUtil.openSession().getMapper(CarMapper.class);List<Car> cars = mapper.selectAll("desc");cars.forEach(car -> System.out.println(car));
}

运行:
9F39C1BA-BFB5-4482-8859-A665AF9BB4F5.png
报错的原因是sql语句不合法,因为采用这种方式传值,最终sql语句会是这样:
select id,car_num as carNum,brand,guide_price as guidePrice,produce_time as produceTime,car_type as carType from t_car order by carNum ‘desc’
desc是一个关键字,不能带单引号的,所以在进行sql语句关键字拼接的时候,必须使用${}

  • 使用${} 改造
<select id="selectAll" resultType="com.powernode.mybatis.pojo.Car">selectid,car_num as carNum,brand,guide_price as guidePrice,produce_time as produceTime,car_type as carTypefromt_car<!--order by carNum #{key}-->order by carNum ${key}
</select>

再次执行测试程序:
BD556528-B6C2-4842-A776-37B26B3B0F43.png

拼接表名

业务背景:实际开发中,有的表数据量非常庞大,可能会采用分表方式进行存储,比如每天生成一张表,表的名字与日期挂钩,例如:2022年8月1日生成的表:t_user20220108。2000年1月1日生成的表:t_user20000101。此时前端在进行查询的时候会提交一个具体的日期,比如前端提交的日期为:2000年1月1日,那么后端就会根据这个日期动态拼接表名为:t_user20000101。有了这个表名之后,将表名拼接到sql语句当中,返回查询结果。那么大家思考一下,拼接表名到sql语句当中应该使用#{} 还是 KaTeX parse error: Expected 'EOF', got '#' at position 9: {} 呢? 使用#̲{}会是这样:select *…{}会是这样:select * from t_car

<select id="selectAllByTableName" resultType="car">selectid,car_num as carNum,brand,guide_price as guidePrice,produce_time as produceTime,car_type as carTypefrom${tableName}
</select>
/*** 根据表名查询所有的Car* @param tableName* @return*/
List<Car> selectAllByTableName(String tableName);
@Test
public void testSelectAllByTableName(){CarMapper mapper = SqlSessionUtil.openSession().getMapper(CarMapper.class);List<Car> cars = mapper.selectAllByTableName("t_car");cars.forEach(car -> System.out.println(car));
}

执行结果:
28B639CD-FD3B-43bb-AB56-6130419BBAA1.png

批量删除

业务背景:一次删除多条记录。
BF64FC68-5697-4695-AD2A-8D32297AED66.png
对应的sql语句:

  • delete from t_user where id = 1 or id = 2 or id = 3;
  • delete from t_user where id in(1, 2, 3);

假设现在使用in的方式处理,前端传过来的字符串:1, 2, 3
如果使用mybatis处理,应该使用#{} 还是 KaTeX parse error: Expected 'EOF', got '#' at position 6: {} 使用#̲{} :delete from…{} :delete from t_user where id in(1, 2, 3)

/*** 根据id批量删除* @param ids* @return*/
int deleteBatch(String ids);
<delete id="deleteBatch">delete from t_car where id in(${ids})
</delete>
@Test
public void testDeleteBatch(){CarMapper mapper = SqlSessionUtil.openSession().getMapper(CarMapper.class);int count = mapper.deleteBatch("1,2,3");System.out.println("删除了几条记录:" + count);SqlSessionUtil.openSession().commit();
}

执行结果:
79F7AB85-F3CA-4c27-873C-CCCDA3D75779.png

模糊查询

需求:查询奔驰系列的汽车。【只要品牌brand中含有奔驰两个字的都查询出来。】

使用${}
/*** 根据品牌进行模糊查询* @param likeBrank* @return*/
List<Car> selectLikeByBrand(String likeBrank);
<select id="selectLikeByBrand" resultType="Car">selectid,car_num as carNum,brand,guide_price as guidePrice,produce_time as produceTime,car_type as carTypefromt_carwherebrand like '%${brand}%'
</select>
@Test
public void testSelectLikeByBrand(){CarMapper mapper = SqlSessionUtil.openSession().getMapper(CarMapper.class);List<Car> cars = mapper.selectLikeByBrand("奔驰");cars.forEach(car -> System.out.println(car));
}

执行结果:
F4BB4FC0-37F3-410b-BE34-C0395C3BD0E6.png

使用#{}

第一种:concat函数

<select id="selectLikeByBrand" resultType="Car">selectid,car_num as carNum,brand,guide_price as guidePrice,produce_time as produceTime,car_type as carTypefromt_carwherebrand like concat('%',#{brand},'%')
</select>

执行结果:
4E799667-0AED-4926-9D08-EDCE3E8C520E.png
第二种:双引号方式

<select id="selectLikeByBrand" resultType="Car">selectid,car_num as carNum,brand,guide_price as guidePrice,produce_time as produceTime,car_type as carTypefromt_carwherebrand like "%"#{brand}"%"
</select>

BCEAB30B-96ED-4cd6-BC49-1D59F0F646D1.png

9.2 typeAliases

我们来观察一下CarMapper.xml中的配置信息:

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapperPUBLIC "-//mybatis.org//DTD Mapper 3.0//EN""http://mybatis.org/dtd/mybatis-3-mapper.dtd"><mapper namespace="com.powernode.mybatis.mapper.CarMapper"><select id="selectAll" resultType="com.powernode.mybatis.pojo.Car">selectid,car_num as carNum,brand,guide_price as guidePrice,produce_time as produceTime,car_type as carTypefromt_carorder by carNum ${key}</select><select id="selectByCarType" resultType="com.powernode.mybatis.pojo.Car">selectid,car_num as carNum,brand,guide_price as guidePrice,produce_time as produceTime,car_type as carTypefromt_carwherecar_type = '${carType}'</select>
</mapper>

resultType属性用来指定查询结果集的封装类型,这个名字太长,可以起别名吗?可以。
在mybatis-config.xml文件中使用typeAliases标签来起别名,包括两种方式:

第一种方式:typeAlias

<typeAliases><typeAlias type="com.powernode.mybatis.pojo.Car" alias="Car"/>
</typeAliases>
  • 首先要注意typeAliases标签的放置位置,如果不清楚的话,可以看看错误提示信息。
  • typeAliases标签中的typeAlias可以写多个。
  • typeAlias:
    • type属性:指定给哪个类起别名
    • alias属性:别名。
      • alias属性不是必须的,如果缺省的话,type属性指定的类型名的简类名作为别名。
      • alias是大小写不敏感的。也就是说假设alias=“Car”,再用的时候,可以CAR,也可以car,也可以Car,都行。

第二种方式:package

如果一个包下的类太多,每个类都要起别名,会导致typeAlias标签配置较多,所以mybatis用提供package的配置方式,只需要指定包名,该包下的所有类都自动起别名,别名就是简类名。并且别名不区分大小写。

<typeAliases><package name="com.powernode.mybatis.pojo"/>
</typeAliases>

package也可以配置多个的。

在SQL映射文件中用一下

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapperPUBLIC "-//mybatis.org//DTD Mapper 3.0//EN""http://mybatis.org/dtd/mybatis-3-mapper.dtd"><mapper namespace="com.powernode.mybatis.mapper.CarMapper"><select id="selectAll" resultType="CAR">selectid,car_num as carNum,brand,guide_price as guidePrice,produce_time as produceTime,car_type as carTypefromt_carorder by carNum ${key}</select><select id="selectByCarType" resultType="car">selectid,car_num as carNum,brand,guide_price as guidePrice,produce_time as produceTime,car_type as carTypefromt_carwherecar_type = '${carType}'</select>
</mapper>

运行测试程序:正常。

9.3 mappers

SQL映射文件的配置方式包括四种:

  • resource:从类路径中加载
  • url:从指定的全限定资源路径中加载
  • class:使用映射器接口实现类的完全限定类名
  • package:将包内的映射器接口实现全部注册为映射器

resource

这种方式是从类路径中加载配置文件,所以这种方式要求SQL映射文件必须放在resources目录下或其子目录下。

<mappers><mapper resource="org/mybatis/builder/AuthorMapper.xml"/><mapper resource="org/mybatis/builder/BlogMapper.xml"/><mapper resource="org/mybatis/builder/PostMapper.xml"/>
</mappers>

url

这种方式显然使用了绝对路径的方式,这种配置对SQL映射文件存放的位置没有要求,随意。

<mappers><mapper url="file:///var/mappers/AuthorMapper.xml"/><mapper url="file:///var/mappers/BlogMapper.xml"/><mapper url="file:///var/mappers/PostMapper.xml"/>
</mappers>

class

如果使用这种方式必须满足以下条件:

  • SQL映射文件和mapper接口放在同一个目录下。
  • SQL映射文件的名字也必须和mapper接口名一致。
<!-- 使用映射器接口实现类的完全限定类名 -->
<mappers><mapper class="org.mybatis.builder.AuthorMapper"/><mapper class="org.mybatis.builder.BlogMapper"/><mapper class="org.mybatis.builder.PostMapper"/>
</mappers>

将CarMapper.xml文件移动到和mapper接口同一个目录下:

  • 在resources目录下新建:com/powernode/mybatis/mapper【这里千万要注意:不能这样新建 com.powernode.mybatis.dao
  • 将CarMapper.xml文件移动到mapper目录下
  • 修改mybatis-config.xml文件
<mappers><mapper class="com.powernode.mybatis.mapper.CarMapper"/>
</mappers>

运行程序:正常!!!

package

如果class较多,可以使用这种package的方式,但前提条件和上一种方式一样。

<!-- 将包内的映射器接口实现全部注册为映射器 -->
<mappers><package name="com.powernode.mybatis.mapper"/>
</mappers>

9.4 idea配置文件模板

mybatis-config.xml和SqlMapper.xml文件可以在IDEA中提前创建好模板,以后通过模板创建配置文件。
7ABBEF88-D7E3-4a73-8411-61CCA49CC3E3.png

9.5 插入数据时获取自动生成的主键

前提是:主键是自动生成的。
业务背景:一个用户有多个角色。
E9F189EB-F5E2-465f-828C-127DB34968FE.png
插入一条新的记录之后,自动生成了主键,而这个主键需要在其他表中使用时。
插入一个用户数据的同时需要给该用户分配角色:需要将生成的用户的id插入到角色表的user_id字段上。
第一种方式:可以先插入用户数据,再写一条查询语句获取id,然后再插入user_id字段。【比较麻烦】
第二种方式:mybatis提供了一种方式更加便捷。

/*** 获取自动生成的主键* @param car*/
void insertUseGeneratedKeys(Car car);
<insert id="insertUseGeneratedKeys" useGeneratedKeys="true" keyProperty="id">insert into t_car(id,car_num,brand,guide_price,produce_time,car_type) values(null,#{carNum},#{brand},#{guidePrice},#{produceTime},#{carType})
</insert>
@Test
public void testInsertUseGeneratedKeys(){CarMapper mapper = SqlSessionUtil.openSession().getMapper(CarMapper.class);Car car = new Car();car.setCarNum("5262");car.setBrand("BYD汉");car.setGuidePrice(30.3);car.setProduceTime("2020-10-11");car.setCarType("新能源");mapper.insertUseGeneratedKeys(car);SqlSessionUtil.openSession().commit();System.out.println(car.getId());
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/477885.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

ES6 模块化语法

目录 ES6 模块化语法 分别暴露 统一暴露 ​编辑 默认暴露 ES6 模块化引入方式 ES6 模块化语法 模块功能主要由两个命令构成&#xff1a;export 和 import。 ⚫ export 命令用于规定模块的对外接口&#xff08;哪些数据需要暴露&#xff0c;就在数据前面加上关键字即可…

【Spring boot】微服务项目的搭建整合swagger的fastdfs和demo的编写

文章目录 1. 微服务项目搭建2. 整合 Swagger 信息3. 部署 fastdfsFastDFS安装环境安装开始图片测试FastDFS和nginx整合在Storage上安装nginxnginx安装不成功排查:4. springboot 整合 fastdfs 的demodemo编写1. 微服务项目搭建 版本总结: spring boot: 2.6.13springfox-boot…

无线电磁波在自由空间的衰减

自由空间损耗&#xff0c;指的是电磁波在空气中传播时候的能量损耗&#xff0c;电磁波在穿透任何介质的时候都会有损耗。在传输路径上的损耗&#xff0c;即为路径损耗。 自由空间路径损耗&#xff08;Free Space Path Loss&#xff09;的基本公式&#xff1a; 简化的自由空间损…

UE5实现可销毁对象的淡化销毁

进入对象材质 设置 的不透明蒙版 不透明蒙版见 UE材质不透明蒙版选项-CSDN博客 默认混合模式(不透明)下无法进行设置&#xff0c;将混合模式修改为 混合模式见 UE5材质混合模式-CSDN博客 新添加Texture sample节点 关于Texture sample&#xff1a;UE5材质Texture Sample …

【Linux学习】【Ubuntu入门】1-7 ubuntu下磁盘管理

1.准备一个U盘或者SD卡&#xff08;插上读卡器&#xff09;&#xff0c;将U盘插入主机电脑&#xff0c;右键点击属性&#xff0c;查看U盘的文件系统确保是FAT32格式 2.右键单击ubuntu右下角图标&#xff0c;将U盘与虚拟机连接 参考链接 3. Ubuntu磁盘文件&#xff1a;/dev/s…

文件的处理(c语言)

首先了解下文件的作用 文件可以把数据直接放在电脑的硬盘上&#xff0c;实现了数据的持久化 什么是文件 文件就是磁盘上的文件。在程序设计中&#xff0c;文件通常有俩种&#xff0c;一种是程序文件&#xff0c;另一种是数据文件&#xff08;这是从文件功能来分类的&#xff…

shell编程之awk

awk 是 Linux 以及 UNIX 环境中现有的功能最强大的数据处理工具。简单地讲&#xff0c; awk 是一种处理文本数据的编程语言。awk 的设计使得它非常适合于处理由行和列组成的文本数据。而在 Linux 或者 UNIX 环境中&#xff0c;这种类型的数据是非常普遍的。 除此之外&#xff…

数据库-基础理论

文章目录 前言一、ORM框架二、ACID原则三、事务Transaction四、N1问题五、Normalization三范式六、FMEA方法论&#xff08;Failure Mode and Effects Analysis&#xff09;七、Profiling和PerformanceSchema查询分析 前言 基础理论 ORM框架、ACID原则、事务Transaction、N1问…

用 Python 从零开始创建神经网络(九):反向传播(Backpropagation)

反向传播&#xff08;Backpropagation&#xff09; 引言1. 分类交叉熵损失导数&#xff08;Categorical Cross-Entropy loss derivative&#xff09;2. 分类交叉熵损失导数 - 代码实现3. Softmax激活函数导数&#xff08;Softmax activation derivative&#xff09;4. Softmax激…

Transformer详解及衍生模型GPT|T5|LLaMa

简介 Transformer 是一种革命性的神经网络架构&#xff0c;首次出现在2017年的论文《Attention Is All You Need》中&#xff0c;由Google的研究团队提出。与传统的RNN和LSTM模型不同&#xff0c;Transformer完全依赖于自注意力&#xff08;Self-Attention&#xff09;机制来捕…

C0034.在Ubuntu中安装的Qt路径

Qt安装路径查询 在终端输入qmake -v如上中/usr/lib/x86_64-linux-gnu就是Qt的安装目录&#xff1b;

Swift 实现查找链表入环点:快慢指针法

文章目录 前言摘要描述题解答案题解代码题解代码分析示例测试及结果时间复杂度空间复杂度总结 前言 本题由于没有合适答案为以往遗留问题&#xff0c;最近有时间将以往遗留问题一一完善。 142. 环形链表 II 不积跬步&#xff0c;无以至千里&#xff1b;不积小流&#xff0c;无…

stable-diffusion-webui在conda pycharm中运行

目录 简介下载conda环境配置环境变量修改launch_utils.py文件运行stable-diffusion-webui下载模型文本生成图片参考 简介 stable-diffusion-webui是AI绘画 Stable Diffusion浏览器UI界面&#xff0c;为用户提供了一个简单、直观的方式来利用 Stable Diffusion 技术创建视觉内容…

小柴冲刺软考中级嵌入式系统设计师系列二、嵌入式系统硬件基础知识(7)嵌入式Soc

越努力&#xff0c;越幸运&#xff01; 分享一个晚霞&#xff0c;真的好美啊&#x1f496;&#xff01; 添加图片注释&#xff0c;不超过 140 字&#xff08;可选&#xff09; 不得不说&#xff0c;我还是喜欢在人少的地方生活啊。 flechazohttps://www.zhihu.com/people/ji…

【云计算网络安全】解析 Amazon 安全服务:构建纵深防御设计最佳实践

文章目录 一、前言二、什么是“纵深安全防御”&#xff1f;三、为什么有必要采用纵深安全防御策略&#xff1f;四、以亚马逊云科技为案例了解纵深安全防御策略设计4.1 原始设计缺少安全策略4.2 外界围栏构建安全边界4.3 访问层安全设计4.4 实例层安全设计4.5 数据层安全设计4.6…

产业用机器人中的旋转花键若损伤有何影响?

旋转花键在产业用机器人中是关键的组件之一&#xff0c;如果机器人中的旋转花键损坏&#xff0c;会对机器人的运行和性能产生一定影响。以下是可能的影响&#xff1a; 1、功能受限&#xff1a;旋转花键用于连接两个旋转部件&#xff08;例如电机轴和传动轴&#xff09;&#xf…

基于STM32的火灾报警装置的Proteus仿真

文章目录 一、火灾报警1.题目要求2.思路2.1 主控2.2 传感器2.3 设定阈值--按键2.4 报警和通风2.5 OLED显示2.6 电源部分2.7 远程终端 3.仿真3.1 未仿真时3.2 仿真开始&#xff0c;界面13.3 切换界面23.4 切换界面3 4.仿真程序4.1 程序说明4.2 主函数4.3 OLED显示函数 二、总结 …

人脸检测开源项目介绍【持续更新】

DeepFace 介绍&#xff1a;DeepFace是一个轻量级的人脸识别和面部属性分析框架&#xff0c;专为Python设计。它集成了多种前沿的深度学习模型&#xff0c;包括VGG-Face、FaceNet、OpenFace、DeepFace、DeepID、ArcFace、Dlib、SFace和GhostFaceNet等&#xff0c;能够进行年龄、…

RabbitMQ 之 死信队列

一、死信的概念 先从概念解释上搞清楚这个定义&#xff0c;死信&#xff0c;顾名思义就是无法被消费的消息&#xff0c;字面意思可以这样理 解&#xff0c;一般来说&#xff0c;producer 将消息投递到 broker 或者直接到 queue 里了&#xff0c;consumer 从 queue 取出消息进行…

使用 LSTM(长短期记忆网络) 模型对时间序列数据(航空旅客人数数据集)进行预测

代码功能 数据准备 加载数据&#xff1a;从公开的航空旅客人数数据集&#xff08;Airline Passengers Dataset&#xff09;中读取时间序列数据。 对数变换和平稳化&#xff1a;对数据应用 log1p 函数减少趋势和波动&#xff0c;使模型更容易学习规律。 归一化处理&#xff1a;…