首先感谢动力节点和杜老师的教学分享!Respect!
学习来源:B站
https://www.bilibili.com/video/BV1JP4y1Z73S?p=1&vd_source=07c8a1a7d89af39fe20c3a6894f5ff6a
资料来源:百度网盘
https://pan.baidu.com/s/1jFuopTaNmRfK4q0BnMfuBw?pwd=dljd
提取码:dljd
目标:
实现功能:
使用技术:
WEB应用的名称:
默认创建的maven web应用没有java和resources目录,包括两种解决方案
web.xml文件的版本较低,可以从tomcat10的样例文件中复制,然后修改
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee
http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd"
version="3.1"
metadata-complete="false">
web-app>
<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/maven-v4_0_0.xsd">
<modelVersion>4.0.0modelVersion>
<groupId>com.powernodegroupId>
<artifactId>mybatis-004-webartifactId>
<packaging>warpackaging>
<version>1.0-SNAPSHOTversion>
<name>mybatis-004-web Maven Webappname>
<url>http://maven.apache.orgurl>
<dependencies>
<dependency>
<groupId>org.mybatisgroupId>
<artifactId>mybatisartifactId>
<version>3.5.10version>
dependency>
<dependency>
<groupId>mysqlgroupId>
<artifactId>mysql-connector-javaartifactId>
<version>8.0.30version>
dependency>
<dependency>
<groupId>ch.qos.logbackgroupId>
<artifactId>logback-classicartifactId>
<version>1.2.11version>
dependency>
<dependency>
<groupId>javax.servletgroupId>
<artifactId>javax.servlet-apiartifactId>
<version>4.0.1version>
dependency>
<dependency>
<groupId>junitgroupId>
<artifactId>junitartifactId>
<version>4.13.2version>
<scope>testscope>
dependency>
dependencies>
<build>
<finalName>mybatis-004-webfinalName>
<plugins>
<plugin>
<groupId>org.apache.maven.pluginsgroupId>
<artifactId>maven-compiler-pluginartifactId>
<configuration>
<source>8source>
<target>8target>
configuration>
plugin>
plugins>
build>
project>
引入相关配置文件,放到resources目录下(全部放到类的根路径下)
DOCTYPE configuration
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
<properties resource="jdbc.properties" />
<environments default="powernode">
<environment id="powernode">
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<property name="driver" value="com.mysql.cj.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/powernode"/>
<property name="username" value="${jdbc.username}"/>
<property name="password" value="${jdbc.password}"/>
dataSource>
environment>
environments>
<mappers>
<mapper resource="AccountMapper.xml"/>
mappers>
configuration>
AccountMapper.xml
logback.xml
jdbc.properties
jdbc.username=root
jdbc.password=root
DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>银行账户转账title>
head>
<body>
<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>
DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>银行转账报告title>
head>
<body>
<h1>转账失败,余额不足!h1>
body>
html>
DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>银行转账报告title>
head>
<body>
<h1>转账失败,未知错误!h1>
body>
html>
DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>银行转账报告title>
head>
<body>
<h1>转账成功!h1>
body>
html>
package com.powernode.bank.pojo;
/**
* 银行账户类
* @author ShiningSong
* @version 1.0
* @since 1.0
*/
public class Account {
private Long id;
private String actno;
private Double balance;
public Account() {
}
public Account(Long id, String actno, Double balance) {
this.id = id;
this.actno = actno;
this.balance = balance;
}
@Override
public String toString() {
return "Account{" +
"id=" + id +
", actno='" + actno + '\'' +
", 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;
}
}
分析dao中⾄少要提供几个方法,才能完成转账:
package com.powernode.bank.dao;
import com.powernode.bank.pojo.Account;
/**
* 账户数据访问对象
* @author ShiningSong
* @version 1.0
* @since 1.0
*/
public interface AccountDao {
/**
* 根据账号获取账户信息
* @param actno 账号
* @return Account 账户信息
*/
Account selectByActno(String actno);
/**
* 更新账户信息
* @param act 账户信息
* @return 1表示更新成功,其他表示失败
*/
int updateByActno(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 {
public Account selectByActno(String actno) {
SqlSession sqlSession = SqlSessionUtil.openSession();
Account act = (Account) sqlSession.selectOne("account.selectByActno", actno);
sqlSession.close();
return act;
}
public int updateByActno(Account act) {
SqlSession sqlSession = SqlSessionUtil.openSession();
int count = sqlSession.update("account.updateByActno", act);
sqlSession.commit();
sqlSession.close();
return count;
}
}
DOCTYPE mapper
PUBLIC "-//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="updateByActno">
update t_act set balance=#{balance} where actno=#{actno}
update>
mapper>
自定义异常类
package com.powernode.bank.exception;
/**
* 余额不足异常
*/
public class MoneyNotEnoughException extends Exception{
public void MoneyNotEnoughException(){}
public MoneyNotEnoughException(String msg) {
super(msg);
}
}
package com.powernode.bank.exception;
/**
* 转账失败异常
*/
public class TransferException extends Exception{
public TransferException() {
}
public TransferException(String message) {
super(message);
}
}
AccountService接口以及AccountServiceImpl
package com.powernode.bank.service;
import com.powernode.bank.exception.MoneyNotEnoughException;
import com.powernode.bank.exception.TransferException;
/**
* 注意:业务类当中的业务方法的名字在起名的时候,最好见名知意,能够体现出具体的业务是做什么的。
* 账户业务类
* @author ShiningSong
* @version 1.0
* @since 1.0
*/
public interface AccountService {
/**
* 账户转账业务
* @param fromActno 转出账号
* @param toActno 转入账号
* @param money 转账金额
*/
void transfer(String fromActno, String toActno, double money) throws MoneyNotEnoughException, TransferException;
}
package com.powernode.bank.service.impl;
import com.powernode.bank.dao.AccountDao;
import com.powernode.bank.dao.impl.AccountDaoImpl;
import com.powernode.bank.exception.MoneyNotEnoughException;
import com.powernode.bank.exception.TransferException;
import com.powernode.bank.pojo.Account;
import com.powernode.bank.service.AccountService;
import com.powernode.bank.utils.GenerateDaoProxy;
import com.powernode.bank.utils.SqlSessionUtil;
import org.apache.ibatis.session.SqlSession;
public class AccountServiceImpl implements AccountService {
// 声明dao层的实现类
private AccountDao accountDaoImpl = new AccountDaoImpl();
public void transfer(String fromActno, String toActno, double money) throws MoneyNotEnoughException, TransferException {
// 1. 判断转出账户的余额是否充足(select)
Account fromAct = accountDaoImpl.selectByActno(fromActno);
// 2. 如果转出账户余额不足,提示用户
if (fromAct.getBalance() < money) {
throw new MoneyNotEnoughException("余额不足,转账失败!");
}
// 3. 如果转出账户余额充足,更新转出账户余额(update)
fromAct.setBalance(fromAct.getBalance() - money);
int count = accountDaoImpl.updateByActno(fromact);
// 4. 更新转入账户余额(update)
Account toAct = accountDaoImpl.selectByActno(toActno);
toAct.setBalance(toAct.getBalance() + money);
count += accountDaoImpl.updateByActno(toAct);
if(2 != count){
throw new TransferException("未知错误,转账失败!");
}
}
}
package com.powernode.bank.web;
import com.powernode.bank.exception.MoneyNotEnoughException;
import com.powernode.bank.exception.TransferException;
import com.powernode.bank.service.AccountService;
import com.powernode.bank.service.impl.AccountServiceImpl;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
@WebServlet("/transfer")
public class AccountServlet extends HttpServlet {
// 为了让其他方法也可以使用这个对象,声明为实例变量
private AccountService accountService = new AccountServiceImpl();
@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
// 获取form表单信息
String fromActno = request.getParameter("fromActno");
String toActno = request.getParameter("toActno");
double money = Double.parseDouble(request.getParameter("money"));
try {
accountService.transfer(fromActno, toActno, money);
// 转账成功页面
response.sendRedirect(request.getContextPath() + "/TransferSuccess.html");
} catch (MoneyNotEnoughException e) {
// 余额不足页面
response.sendRedirect(request.getContextPath() + "/MoneyNotEnoughException.html");
} catch (TransferException e) {
// 转账异常页面
response.sendRedirect(request.getContextPath() + "/TransferException.html");
} catch (Exception e){
// 转账异常页面
response.sendRedirect(request.getContextPath() + "/TransferException.html");
}
}
}
SqlSessionFactoryBuilder
这个类可以被实例化、使用和丢弃,一旦创建了 SqlSessionFactory,就不再需要它了。 因此 SqlSessionFactoryBuilder 实例的最佳作用域是方法作用域(也就是局部方法变量)。
你可以重用SqlSessionFactoryBuilder 来创建多个 SqlSessionFactory 实例,但最好还是不要一直保留着它,以保证所有 的 XML 解析资源可以被释放给更重要的事情。
SqlSessionFactory
SqlSession
try(SqlSession sqlSession = sqlSessionFactory.openSession()){
// 应用逻辑代码
}
package com.powernode.bank.service.impl;
import com.powernode.bank.dao.AccountDao;
import com.powernode.bank.dao.impl.AccountDaoImpl;
import com.powernode.bank.exception.MoneyNotEnoughException;
import com.powernode.bank.exception.TransferException;
import com.powernode.bank.pojo.Account;
import com.powernode.bank.service.AccountService;
import com.powernode.bank.utils.GenerateDaoProxy;
import com.powernode.bank.utils.SqlSessionUtil;
import org.apache.ibatis.session.SqlSession;
public class AccountServiceImpl implements AccountService {
// 声明dao层的实现类
private AccountDao accountDaoImpl = new AccountDaoImpl();
public void transfer(String fromActno, String toActno, double money) throws MoneyNotEnoughException, TransferException {
// 1. 判断转出账户的余额是否充足(select)
Account fromAct = accountDaoImpl.selectByActno(fromActno);
// 2. 如果转出账户余额不足,提示用户
if (fromAct.getBalance() < money) {
throw new MoneyNotEnoughException("余额不足,转账失败!");
}
// 3. 如果转出账户余额充足,更新转出账户余额(update)
fromAct.setBalance(fromAct.getBalance() - money);
int count = accountDaoImpl.updateByActno(fromact);
// 模拟异常
String exception = null;
exception.toString();
// 4. 更新转入账户余额(update)
Account toAct = accountDaoImpl.selectByActno(toActno);
toAct.setBalance(toAct.getBalance() + money);
count += accountDaoImpl.updateByActno(toAct);
if(2 != count){
throw new TransferException("未知错误,转账失败!");
}
}
}
事务出问题了,转账失败了,钱仍然是少了1万。这是什么原因呢?主要是因为service 和dao中使用的SqlSession对象不是同一个。
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;
import java.io.IOException;
/**
* MyBatis工具类
* @author Shanglinsong
* @version 1.0
* @sine 1.0
*/
public class SqlSessionUtil {
// 工具类的构造方法一般都是私有化的。
// 工具类中所有的方法都是静态的,直接采用类名调用即可,不需要new对象。
// 为了防止new对象,构造方法私有化
private SqlSessionUtil() {
}
private static SqlSessionFactory sqlSessionFactory;
// 类加载时执行
// SqlSessionUtil工具类在进行第一次加载的时候,解析mybatis-config.xml文件,创建SqlSessionFactory对象。
static{
try {
sqlSessionFactory = new SqlSessionFactoryBuilder().build(Resources.getResourceAsStream("mybatis-config.xml"));
} catch (IOException e) {
throw new RuntimeException(e);
}
}
// 全局的,服务器级别的,一个服务器当中定义一个即可。
// 为什么把SqlSession对象放到ThreadLocal当中呢?为了保证一个线程对应一个SqlSession。
private static ThreadLocal<SqlSession> local = new ThreadLocal<SqlSession>();
/**
* 获取SqlSession对象
* @return SqlSession 会话对象
*/
public static SqlSession openSession(){
SqlSession sqlSession = local.get();
if (sqlSession == null) {
sqlSession = sqlSessionFactory.openSession();
// 将sqlSession对象绑定到当前线程上。
local.set(sqlSession);
}
return sqlSession;
}
/**
* 关闭会话,释放资源,同时与当前线程解绑
* @param sqlSession 当前线程使用的会话
*/
public static void close(SqlSession sqlSession){
if (sqlSession != null) {
sqlSession.close();
// 注意移除SqlSession对象与当前线程的绑定关系。
// 因为Tomcat服务器支持线程池。也就是说:用过的线程对象t1,可能下一次还会使用这个t1线程
local.remove();
}
}
}
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 {
public Account selectByActno(String actno) {
SqlSession sqlSession = SqlSessionUtil.openSession();
// Account act = (Account) sqlSession.selectOne("account.selectByActno", actno);
// sqlSession.close();
// return act;
return (Account) sqlSession.selectOne("account.selectByActno", actno);
}
public int updateByActno(Account act) {
SqlSession sqlSession = SqlSessionUtil.openSession();
// int count = sqlSession.update("account.updateByActno", act);
// sqlSession.commit();
// sqlSession.close();
// return count;
return sqlSession.update("account.updateByActno", act);
}
}
package com.powernode.bank.service.impl;
import com.powernode.bank.dao.AccountDao;
//import com.powernode.bank.dao.impl.AccountDaoImpl;
import com.powernode.bank.exception.MoneyNotEnoughException;
import com.powernode.bank.exception.TransferException;
import com.powernode.bank.pojo.Account;
import com.powernode.bank.service.AccountService;
import com.powernode.bank.utils.GenerateDaoProxy;
import com.powernode.bank.utils.SqlSessionUtil;
import org.apache.ibatis.session.SqlSession;
public class AccountServiceImpl implements AccountService {
// 声明dao层的实现类
private AccountDao accountDaoImpl = new AccountDaoImpl();
public void transfer(String fromActno, String toActno, double money) throws MoneyNotEnoughException, TransferException {
// 开启会话
SqlSession sqlSession = SqlSessionUtil.openSession();
// 1. 判断转出账户的余额是否充足(select)
Account fromAct = accountDaoImpl.selectByActno(fromActno);
// 2. 如果转出账户余额不足,提示用户
if (fromAct.getBalance() < money) {
throw new MoneyNotEnoughException("余额不足,转账失败!");
}
// 3. 如果转出账户余额充足,更新转出账户余额(update)
fromAct.setBalance(fromAct.getBalance() - money);
int count = accountDaoImpl.updateByActno(fromact);
// 4. 更新转入账户余额(update)
Account toAct = accountDaoImpl.selectByActno(toActno);
toAct.setBalance(toAct.getBalance() + money);
count += accountDaoImpl.updateByActno(toAct);
if(2 != count){
throw new TransferException("未知错误,转账失败!");
}
// 提交事务
sqlSession.commit();
// 关闭资源
SqlSessionUtil.close(sqlSession);
}
}
查看数据库数据没有变化:
去除异常查看功能是否可以实现:
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 {
public Account selectByActno(String actno) {
SqlSession sqlSession = SqlSessionUtil.openSession();
return (Account) sqlSession.selectOne("account.selectByActno", actno);
}
public int updateByActno(Account act) {
SqlSession sqlSession = SqlSessionUtil.openSession();
return sqlSession.update("account.updateByActno", act);
}
}
我们不难发现,这个dao实现类中的方法代码很固定,基本上就是一行代码,通过SqlSession对象调用 insert、delete、update、select等方法,这个类中的方法没有任何业务逻辑,既然是这样,这个类我们能不能动态的生成,以后可以不写这个类吗?
答案:可以。
Javassist是一个开源的分析、编辑和创建Java字节码的类库。是由东京工业大学的数学和计算机科学 系的 Shigeru Chiba (千叶 滋)所创建的。它已加入了开放源代码JBoss 应用服务器项目,通过使用 Javassist对字节码操作为JBoss实现动态"AOP"框架
<dependency>
<groupId>org.javassistgroupId>
<artifactId>javassistartifactId>
<version>3.21.0-GAversion>
dependency>
package com.powernode.javassist.test;
import javassist.ClassPool;
import javassist.CtClass;
import javassist.CtMethod;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
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 execute = aClass.getDeclaredMethod("execute");
execute.invoke(o);
}
}
运行要注意:加两个参数,要不然会有异常。(本人jdk1.8没配置未出现异常)
课堂测试:
package com.powernode.dao;
public interface AccountDao {
void delete();
int insert(String actno);
int update(String actno, Double balance);
String selectByActno(String actno);
}
@Test
public void testGenerateFirstClass() throws Exception{
// 获取类池,这个类池就是用来给我生成class的
ClassPool pool = ClassPool.getDefault();
// 制造类(需要告诉javassist,类名是啥)
CtClass ctClass = pool.makeClass("com.powernode.dao.impl.AccountDaoImpl");
// 制造方法
String methodCode = "public void insert(){System.out.println(123);}";
CtMethod ctMethod = CtMethod.make(methodCode, ctClass);
// 将方法添加到类中
ctClass.addMethod(ctMethod);
// 在内存中生成class
ctClass.toClass();
// 类加载到JVM当中,返回AccountDaoImpl类的字节码
Class<?> clazz = Class.forName("com.powernode.dao.impl.AccountDaoImpl");
// 创建对象
Object obj = clazz.newInstance();
// 获取AccountDaoImpl中的insert方法
Method insertMethod = clazz.getDeclaredMethod("insert");
// 调用方法insert
insertMethod.invoke(obj);
}
运行结果:
@Test
public void testGenerateImpl() throws Exception{
// 获取类池
ClassPool pool = ClassPool.getDefault();
// 制造类
CtClass ctClass = pool.makeClass("com.powernode.dao.impl.AccountDaoImpl");
// 制造接口
CtClass ctInterface = pool.makeInterface("com.powernode.dao.AccountDao");
// 添加接口到类中
ctClass.addInterface(ctInterface); // AccountDaoImpl implements AccountDao
// 实现接口中的方法
// 制造方法
CtMethod ctMethod = CtMethod.make("public void delete(){System.out.println(\"hello delete!\");}", ctClass);
// 将方法添加到类中
ctClass.addMethod(ctMethod);
// 在内存中生成类,同时将生成的类加载到JVM当中。
Class<?> clazz = ctClass.toClass();
AccountDao accountDao = (AccountDao)clazz.newInstance();
accountDao.delete();
}
执行结果:
@Test
public void testGenerateAccountDaoImpl() throws Exception{
// 获取类池
ClassPool pool = ClassPool.getDefault();
// 制造类
CtClass ctClass = pool.makeClass("com.powernode.impl.AccountDaoImpl");
// 制造接口
CtClass ctInterface = pool.makeInterface("com.powernode.dao.AccountDao");
// 实现接口
ctClass.addInterface(ctInterface);
// 实现接口中所有的方法
// 获取接口中所有的方法
Method[] methods = AccountDao.class.getDeclaredMethods();
Arrays.stream(methods).forEach(method -> {
// method是接口中的抽象方法
// 把method抽象方法给实现了。
try {
// public void delete(){}
// public int update(String actno, Double balance){}
StringBuilder methodCode = new StringBuilder();
methodCode.append("public "); // 追加修饰符列表
methodCode.append(method.getReturnType().getName()); // 追加返回值类型
methodCode.append(" ");
methodCode.append(method.getName()); //追加方法名
methodCode.append("(");
// 拼接参数 String actno, Double balance
Class<?>[] parameterTypes = method.getParameterTypes();
for (int i = 0; i < parameterTypes.length; i++) {
Class<?> parameterType = parameterTypes[i];
methodCode.append(parameterType.getName());
methodCode.append(" ");
methodCode.append("arg" + i);
if(i != parameterTypes.length - 1){
methodCode.append(",");
}
}
methodCode.append("){System.out.println(11111); ");
// 动态的添加return语句
String returnTypeSimpleName = method.getReturnType().getSimpleName();
if ("void".equals(returnTypeSimpleName)) {
}else if("int".equals(returnTypeSimpleName)){
methodCode.append("return 1;");
}else if("String".equals(returnTypeSimpleName)){
methodCode.append("return \"hello\";");
}
methodCode.append("}");
System.out.println(methodCode);
CtMethod ctMethod = CtMethod.make(methodCode.toString(), ctClass);
ctClass.addMethod(ctMethod);
} catch (Exception e) {
e.printStackTrace();
}
});
// 在内存中生成class,并且加载到JVM当中
Class<?> clazz = ctClass.toClass();
// 创建对象
AccountDao accountDao = (AccountDao) clazz.newInstance();
// 调用方法
accountDao.insert("aaaaa");
accountDao.delete();
accountDao.update("aaaa", 1000.0);
accountDao.selectByActno("aaaa");
}
执行结果:
package com.powernode.bank.utils;
import com.powernode.bank.dao.AccountDao;
import org.apache.ibatis.javassist.ClassPool;
import org.apache.ibatis.javassist.CtClass;
import org.apache.ibatis.javassist.CtMethod;
import org.apache.ibatis.mapping.SqlCommandType;
import org.apache.ibatis.session.SqlSession;
import java.lang.reflect.Method;
import java.util.Arrays;
/**
* 工具类:可以动态生成DAO实现类。(或者说可以动态生成DAO的代理类)
* 注意!!!!非常重要!!!
* 凡是使用GenerateDaoProxy的,SQLMapper.xml映射文件中的namespace必须是dao接口的全名,id必须是dao接口中的方法名。
*
* @author ShiningSong
* @version 1.0
* @since 1.0
*/
public class GenerateDaoProxy {
/**
* 生成dao接口实现类,并且将累的对象创建出来并返回
*
* @param sqlSession
* @param daoInterface dao接口
* @return dao接口实现类的实例化对象
*/
public static Object generate(SqlSession sqlSession, Class daoInterface) {
// 类池
ClassPool pool = ClassPool.getDefault();
// 制造类(com.powernode.bank.dao.AccountDao --> com.powernode.bank.dao.AccountDaoProxy)
CtClass ctClass = pool.makeClass(daoInterface.getName() + "Proxy");
// 制造接口
CtClass ctInterface = pool.makeInterface(daoInterface.getName());
// 实现接口
ctClass.addInterface(ctInterface);
// 实现接口的所有
Method[] methods = daoInterface.getDeclaredMethods();
Arrays.stream(methods).forEach(method -> {
// method是这个接口中的抽象方法
// 将method这个抽象方法进行实现
try {
// Account selectByActno(String actno);
// public Account selectByActno(String actno){代码};
StringBuilder methodCode = new StringBuilder();
methodCode.append("public ");
methodCode.append(method.getReturnType().getName());
methodCode.append(" ");
methodCode.append(method.getName());
methodCode.append("(");
// 需要方法的形式参数列表
Class<?>[] parameterTypes = method.getParameterTypes();
for (int i = 0; i < parameterTypes.length; i++) {
methodCode.append(parameterTypes[i].getName());
methodCode.append(" ");
methodCode.append("arg" + i);
if (i < parameterTypes.length-1) {
methodCode.append(", ");
}
}
methodCode.append("){\n");
// 需要方法体当中的代码
methodCode.append("org.apache.ibatis.session.SqlSession sqlSession = com.powernode.bank.utils.SqlSessionUtil.openSession();\n");
// 需要知道是什么类型的sql语句
// sql语句的id是框架使用者提供的,具有多变性,对于框架的开发人员来说,是不知道具体内容的。
// 既然框架开发者不知道sqlId,怎么办呢?mybatis框架的开发者于是就出台了一个规定:凡是使用GenerateDaoProxy机制的,sqlId都不能随便写。
// namespace必须是dao接口的全限定名称,id必须是dao接口中的方法名。
String sqlId = daoInterface.getName() + "." + method.getName();
// System.out.println(sqlId);
SqlCommandType sqlCommandType = sqlSession.getConfiguration().getMappedStatement(sqlId).getSqlCommandType();
switch (sqlCommandType){
case INSERT: break;
case DELETE: break;
case UPDATE: {
methodCode.append("return sqlSession.update(\""+sqlId+"\", arg0);");
}break;
case SELECT: {
methodCode.append("return ("+method.getReturnType().getName()+")sqlSession.selectOne(\""+sqlId+"\", arg0);");
}break;
}
methodCode.append("\n}");
System.out.println(methodCode);
CtMethod ctMethod = CtMethod.make(methodCode.toString(), ctClass);
ctClass.addMethod(ctMethod);
} catch (Exception e) {
e.printStackTrace();
}
});
// 创建对象
Object obj = null;
try {
Class<?> clazz = ctClass.toClass();
obj = clazz.newInstance();
} catch (Exception e) {
e.printStackTrace();
}
return obj;
}
public static void main(String[] args) {
SqlSession sqlSession = SqlSessionUtil.openSession();
Object obj = GenerateDaoProxy.generate(sqlSession, AccountDao.class);
System.out.println(obj.toString());
}
}
DOCTYPE mapper
PUBLIC "-//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="updateByActno">
update t_act set balance=#{balance} where actno=#{actno}
update>
mapper>
注:本人在采用该方法时遇见以下问题,在寻找许多解决方案后仍然无果,不知道是不是版本问题导致的,但由于不是核心问题(因为采用mybatis提供的getMapper()方法可以正常使用),因此打算暂时搁置下来,等将来再常识解决。
网页端:
服务器端:
正常情况下继续:
以上所讲内容mybatis内部已经实现了。直接调用以下代码即可获取dao接口的代理类:
// 在mybatis当中,mybatis提供了相关的机制。也可以动态为我们生成dao接口的实现类。(代理类:dao接口的代理)
// mybatis当中实际上采用了代理模式。在内存中生成dao接口的代理类,然后创建代理类的实例。
// 使用mybatis的这种代理机制的前提:SqlMapper.xml文件中namespace必须是dao接口的全限定名称,id必须是dao接口中的方法名。
// 怎么用?代码怎么写?AccountDao accountDao = sqlSession.getMapper(AccountDao.class);
private AccountDao accountDao = SqlSessionUtil.openSession().getMapper(AccountDao.class);
建包:
引入类:
package com.powernode.mybatis.mapper;
// 包名也有叫做:com.powernode.mybatis.dao
// mapper包就是dao包。
import com.powernode.mybatis.pojo.Car;
import java.util.List;
/**
* CarMapper类,提供crud方法接口
* 一般使用mybatis的话,一般不叫做XXXDao了。一般都是XXXMapper。
* @author ShiningSong
* @version 1.0
* @since 1.0
*/
public interface CarMapper {
/**
* 新增汽车信息
* @param car
* @return
*/
int insert(Car car);
/**
* 根据id删除汽车信息
* @param id
* @return
*/
int deleteById(Long id);
/**
* 根据id更新汽车信息
* @param car
* @return
*/
int updateById(Car car);
/**
* 根据id查询汽车信息
* @param id
* @return
*/
Car selectById(Long id);
/**
* 查询所有汽车信息
* @return
*/
List<Car> selectAll();
}
引入配置文件:
DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.powernode.mybatis.mapper.CarMapper">
<insert id="insert">
insert into t_car values(null, #{carNum}, #{brand}, #{guidePrice}, #{produceTime}, #{carType})
insert>
<delete id="deleteById">
delete from t_car where id=#{id}
delete>
<update id="updateById">
update t_car
set car_num=#{carNum},
brand=#{brand},
guide_price=#{guidePrice},
produce_time=#{produceTime},
car_type=#{carType}
where id=#{id}
update>
<select id="selectById" resultType="com.powernode.mybatis.pojo.Car">
select
id,
car_num as carNum,
brand,
guide_price as guidePrice,
produce_time as produceTime,
car_type as carType
from t_car
where id=#{id}
select>
<select id="selectAll" resultType="com.powernode.mybatis.pojo.Car">
select
id,
car_num as carNum,
brand,
guide_price as guidePrice,
produce_time as produceTime,
car_type as carType
from t_car
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.apache.ibatis.session.SqlSession;
import org.junit.Test;
import java.util.List;
public class CarCRUDTest {
@Test
public void testSelectAll(){
SqlSession sqlSession = SqlSessionUtil.openSession();
CarMapper mapper = sqlSession.getMapper(CarMapper.class);
List<Car> cars = mapper.selectAll();
cars.forEach(car -> System.out.println(car));
sqlSession.close();
}
@Test
public void testSelectById(){
SqlSession sqlSession = SqlSessionUtil.openSession();
CarMapper mapper = sqlSession.getMapper(CarMapper.class);
Car car = mapper.selectById(15L);
System.out.println(car);
sqlSession.close();
}
@Test
public void testUpdateById(){
SqlSession sqlSession = SqlSessionUtil.openSession();
CarMapper mapper = sqlSession.getMapper(CarMapper.class);
Car car = new Car(15L, "7777", "布加迪威龙", 1000.0, "2015-01-19", "燃油车");
int count = mapper.updateById(car);
System.out.println(count);
sqlSession.commit();
sqlSession.close();
}
@Test
public void testDeleteById(){
SqlSession sqlSession = SqlSessionUtil.openSession();
CarMapper mapper = sqlSession.getMapper(CarMapper.class);
int count = mapper.deleteById(14L);
System.out.println(count);
sqlSession.commit();
sqlSession.close();
}
@Test
public void testInsert(){
SqlSession sqlSession = SqlSessionUtil.openSession();
// 面向接口获取接口的代理对象
CarMapper mapper = sqlSession.getMapper(CarMapper.class);
Car car = new Car(null, "6666", "奔驰600", 60.0, "2011-12-03", "燃油车");
int count = mapper.insert(car);
System.out.println("插入了几条数据:" + count);
sqlSession.commit();
sqlSession.close();
}
}
#{}:先编译sql语句,再给占位符传值,底层是PreparedStatement实现。可以防止sql注入,比较常用。
${}:先进行sql语句拼接,然后再编译sql语句,底层是Statement实现。存在sql注入现象。只有在需要 进行sql语句关键字拼接的情况下才会用到。
需求:根据car_type查询汽车
模块名:mybatis-005-antic
依赖:
package com.powernode.mybatis.mapper;
import com.powernode.mybatis.pojo.Car;
import java.util.List;
/**
* Car的sql映射对象
* @author ShiningSong
* @version 1.0
* @since 1.0
*/
public interface CarMapper {
/**
* 根据car_num获取Car
* @param CarType
* @return
*/
List<Car> selectByCarType(String CarType);
}
DOCTYPE mapper
PUBLIC "-//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">
select
id,
car_num as carNum,
brand,
guide_price as guidePrice,
produce_time as produceTime,
car_type as carType
from t_car
where car_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.apache.ibatis.session.SqlSession;
import org.junit.Test;
import java.util.List;
public class CarMapperTest {
@Test
public void testSelectByCarType(){
SqlSession sqlSession = SqlSessionUtil.openSession();
CarMapper mapper = sqlSession.getMapper(CarMapper.class);
List<Car> cars = mapper.selectByCarType("燃油车");
cars.forEach(car -> System.out.println(car));
sqlSession.close();
}
}
执行结果:
同样的需求,我们使用${}来完成
CarMapper.xml文件修改如下:
${} 是先进行sql语句的拼接,然后再编译,出现语法错误是正常的,因为 燃油车 是一个字符串,在sql语句中应该添加单引号
修改CarMapper.xml
DOCTYPE mapper
PUBLIC "-//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">
select
id,
car_num as carNum,
brand,
guide_price as guidePrice,
produce_time as produceTime,
car_type as carType
from t_car
where
car_type='${carType}'
select>
mapper>
通过以上测试,可以看出,对于以上这种需求来说,还是建议使用 #{} 的方式。
原则:能用 #{} 就不用 ${}
当需要进行sql语句关键字拼接的时候。必须使用${}
需求:通过向sql语句中注入asc或desc关键字,来完成数据的升序或降序排列。
先使用#{}尝试:
CarMappe接口:
/**
* 查询所有的Car
* @param ascOrDesc asc或者desc
* @return
*/
List<Car> selectAllOrderByCarNum(String ascOrDesc);
CarMapper.xml
<select id="selectAllOrderByCarNum" resultType="com.powernode.mybatis.pojo.Car">
select
id,
car_num as carNum,
brand,
guide_price as guidePrice,
produce_time as produceTime,
car_type as carType
from t_car
order by car_num #{key}
select>
测试程序:
@Test
public void testSelectAllOrderByCarNum(){
SqlSession sqlSession = SqlSessionUtil.openSession();
CarMapper mapper = sqlSession.getMapper(CarMapper.class);
List<Car> cars = mapper.selectAllOrderByCarNum("desc");
cars.forEach(car -> System.out.println(car));
sqlSession.close();
}
报错的原因是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 car_num 'desc'
desc是一个关键字,不能带单引号的,所以在进行sql语句关键字拼接的时候,必须使用${}
使用${} 改造
CarMapper.xml
<select id="selectAllOrderByCarNum" resultType="com.powernode.mybatis.pojo.Car">
select
id,
car_num as carNum,
brand,
guide_price as guidePrice,
produce_time as produceTime,
car_type as carType
from t_car
order by car_num ${key}
select>
再次测试结果:
业务背景:实际开发中,有的表数据量非常庞大,可能会采用分表方式进行存储,比如每天生成一张表,表的名字与日期挂钩,例如:2022年8月1日生成的表:t_user20220108。2000年1月1日生成的表:t_user20000101。此时前端在进行查询的时候会提交一个具体的日期,比如前端提交的日期为: 2000年1月1日,那么后端就会根据这个日期动态拼接表名为:t_user20000101。有了这个表名之后,将 表名拼接到sql语句当中,返回查询结果。那么大家思考一下,拼接表名到sql语句当中应该使用#{} 还是 ${} 呢?
使用#{}会是这样:select * from ‘t_car’
使用${}会是这样:select * from t_car
CarMapper.xml
<select id="selectAllByTableName" resultType="com.powernode.mybatis.pojo.Car">
select
id,
car_num as carNum,
brand,
guide_price as guidePrice,
produce_time as produceTime,
car_type as carType
from ${tableName}
select>
CarMapper接口
/**
* 根据表名查询所有的Car
* @param tableName
* @return
*/
List<Car> selectAllByTableName(String tableName);
测试类:
@Test
public void testSelectAllByTableName(){
SqlSession sqlSession = SqlSessionUtil.openSession();
CarMapper mapper = sqlSession.getMapper(CarMapper.class);
List<Car> cars = mapper.selectAllByTableName("t_car");
cars.forEach(car -> System.out.println(car));
sqlSession.close();
}
执行结果:
业务背景:一次删除多条记录。
对应的sql语句:
假设现在使用in的方式处理,前端传过来的字符串:1, 2, 3
如果使用mybatis处理,应该使用#{} 还是 ${}
使用#{} :delete from t_user where id in(‘1,2,3’)
使用${} :delete from t_user where id in(1, 2, 3)
CarMapper接口
/**
* 根据id批量删除
* @param ids
* @return
*/
int deleteBatch(String ids);
CarMapper.xml
<delete id="deleteBatch">
delete from t_car where id in (${ids})
delete>
测试类:
@Test
public void testDeleteBatch(){
SqlSession sqlSession = SqlSessionUtil.openSession();
CarMapper mapper = sqlSession.getMapper(CarMapper.class);
int count = mapper.deleteBatch("11, 19, 36");
System.out.println(count);
sqlSession.commit();
sqlSession.close();
}
执行结果:
需求:查询奔驰系列的汽车。【只要品牌brand中含有奔驰两个字的都查询出来。】
使用${}
CarMapper接口
/**
* 根据品牌进行模糊查询
* @param likeBrand
* @return
*/
List<Car> selectLikeBrand(String likeBrand);
CarMapper.xml
<select id="selectLikeBrand" resultType="com.powernode.mybatis.pojo.Car">
select
id,
car_num as carNum,
brand,
guide_price as guidePrice,
produce_time as produceTime,
car_type as carType
from t_car
where brand like '%${brand}%'
select>
测试类:
@Test
public void testSelectLikeBrand(){
SqlSession sqlSession = SqlSessionUtil.openSession();
CarMapper mapper = sqlSession.getMapper(CarMapper.class);
List<Car> cars = mapper.selectLikeBrand("奔驰");
cars.forEach(car -> System.out.println(car));
sqlSession.close();
}
执行结果:
使用#{}
第一种:concat函数
CarMapper.xml
<select id="selectLikeBrand" resultType="com.powernode.mybatis.pojo.Car">
select
id,
car_num as carNum,
brand,
guide_price as guidePrice,
produce_time as produceTime,
car_type as carType
from t_car
where
brand like concat('%', #{brand}, '%')
select>
执行结果:
<typeAliases>
<typeAlias type="com.powernode.mybatis.pojo.Car" alias="Car"/>
typeAliases>
如果一个包下的类太多,每个类都要起别名,会导致typeAlias标签配置较多,所以mybatis用提供 package的配置方式,只需要指定包名,该包下的所有类都自动起别名,别名就是简类名。并且别名不区分大小写。
package也可以配置多个的。
<typeAliases>
<package name="com.powernode.mybatis.pojo"/>
typeAliases>
<mappers>
<mapper resource="CarMapper.xml"/>
mappers>
这种方式显然使用了绝对路径的方式,这种配置对SQL映射文件存放的位置没有要求,随意。
<mappers>
<mapper url="file:///E:\ReLearn_MyBatis_Workspace\mybatis-006-antic\src\main\resources\CarMapper.xml"/>
mappers>
如果使用这种方式必须满足以下条件:
思考:mapper标签的作用是指定SqlMapper.xml文件的路径,指定接口名有什么用呢?
在resources目录下新建:com/powernode/mybatis/mapper
将CarMapper.xml文件移动到mapper目录下
修改mybatis-config.xml文件
<mappers>
<mapper class="com.powernode.mybatis.mapper.CarMapper"/>
mappers>
<mappers>
<package name="com.powernode.mybatis.mapper"/>
mappers>
mybatis-config.xml和SqlMapper.xml文件可以在IDEA中提前创建好模板,以后通过模板创建配置文 件。
前提是:主键是自动生成的。
业务背景:一个用户有多个角色。
第一种方式:可以先插入用户数据,再写一条查询语句获取id,然后再插入user_id字段。【比较麻烦】
第二种方式:mybatis提供了一种方式更加便捷:
CarMapper接口
/**
* 获取自动生成的主键
* @param car
*/
int insertGetGeneratedKeys(Car car);
CarMapper.xml
<insert id="insertGetGeneratedKeys" useGeneratedKeys="true" keyProperty="id">
insert into t_car
values (null, #{carNum}, #{brand}, #{guidePrice}, #{produceTime}, #{carType})
insert>
测试类:
@Test
public void testInsertGetGeneratedKeys(){
SqlSession sqlSession = SqlSessionUtil.openSession();
CarMapper mapper = sqlSession.getMapper(CarMapper.class);
Car car = new Car(null, "8888", "劳斯莱斯幻影", 920.0, "2018-09-10", "燃油车");
int count = mapper.insertGetGeneratedKeys(car);
System.out.println("插入了几条数据:" + count);
sqlSession.commit();
System.out.println("自动生成的CarId为:" + car.getId());
sqlSession.close();
}
执行结果:
mybatis小技巧
1. #{}和${}的区别
#{}的执行结果:
[main] DEBUG c.p.mybatis.mapper.CarMapper.selectByCarType - ==> Preparing: select id, car_num as carNum, brand, guide_price as guidePrice, produce_time as produceTime, car_type as carType from t_car where car_type = ?
[main] DEBUG c.p.mybatis.mapper.CarMapper.selectByCarType - ==> Parameters: 新能源(String)
[main] DEBUG c.p.mybatis.mapper.CarMapper.selectByCarType - <== Total: 2
${}的执行结果:
[main] DEBUG c.p.mybatis.mapper.CarMapper.selectByCarType - ==> Preparing: select id, car_num as carNum, brand, guide_price as guidePrice, produce_time as produceTime, car_type as carType from t_car where car_type = 新能源
[main] DEBUG c.p.mybatis.mapper.CarMapper.selectByCarType - ==> Parameters:
org.apache.ibatis.exceptions.PersistenceException:
### Error querying database. Cause: java.sql.SQLSyntaxErrorException: Unknown column '新能源' in 'where clause'
### The error may exist in CarMapper.xml
### The error may involve defaultParameterMap
### The error occurred while setting parameters
### SQL: select id, car_num as carNum, brand, guide_price as guidePrice, produce_time as produceTime, car_type as carType from t_car where car_type = 新能源
### Cause: java.sql.SQLSyntaxErrorException: Unknown column '新能源' in 'where clause'
#{}和${}的区别:
#{}: 底层使用PreparedStatement。特点:先进行SQL语句的编译,然后给SQL语句的占位符问号?传值。可以避免SQL注入的风险。
${}:底层使用Statement。特点:先进行SQL语句的拼接,然后再对SQL语句进行编译。存在SQL注入的风险。
优先使用#{},这是原则。避免SQL注入的风险。
#{}的执行结果:
Preparing: select
id, car_num as carNum, brand, guide_price as guidePrice, produce_time as produceTime, car_type as carType
from t_car order by produce_time ?
Parameters: asc(String)
select
id, car_num as carNum, brand, guide_price as guidePrice, produce_time as produceTime, car_type as carType
from t_car order by produce_time 'asc'
${}的执行结果:
Preparing:
select id, car_num as carNum, brand, guide_price as guidePrice, produce_time as produceTime, car_type as carType
from t_car order by produce_time asc
Parameters:
如果需要SQL语句的关键字放到SQL语句中,只能使用${},因为#{}是以值的形式放到SQL语句当中的。
2. 向SQL语句当中拼接表名,就需要使用${}
现实业务当中,可能会存在分表存储数据的情况。因为一张表存的话,数据量太大。查询效率比较低。
可以将这些数据有规律的分表存储,这样在查询的时候效率就比较高。因为扫描的数据量变少了。
日志表:专门存储日志信息的。如果t_log只有一张表,这张表中每一天都会产生很多log,慢慢的,这个表中数据会很多。
怎么解决问题?
可以每天生成一个新表。每张表以当天日期作为名称,例如:
t_log_20220901
t_log_20220902
....
你想知道某一天的日志信息怎么办?
假设今天是20220901,那么直接查:t_log_20220901的表即可。
3.批量删除:一次删除多条记录。
批量删除的SQL语句有两种写法:
第一种or:delete from t_car where id=1 or id=2 or id=3;
第二种int:delete from t_car where id in(1,2,3);
应该采用${}的方式:
delete from t_car where id in(${ids});
4.模糊查询:like
需求:根据汽车品牌进行模糊查询
select * from t_car where brand like '%奔驰%';
select * from t_car where brand like '%比亚迪%';
第一种方案:
'%${brand}%'
第二种方案:concat函数,这个是mysql数据库当中的一个函数,专门进行字符串拼接
concat('%',#{brand},'%')
第三种方案:比较鸡肋了。可以不算。
concat('%','${brand}','%')
第四种方案:
"%"#{brand}"%"
5. 关于MyBatis中别名机制:
所有别名不区分大小写。
namespace不能使用别名机制。
6. mybatis-config.xml文件中的mappers标签。
要求类的根路径下必须有:CarMapper.xml
要求在d:/下有CarMapper.xml文件
mapper标签的属性可以有三个:
resource:这种方式是从类的根路径下开始查找资源。采用这种方式的话,你的配置文件需要放到类路径当中才行。
url: 这种方式是一种绝对路径的方式,这种方式不要求配置文件必须放到类路径当中,哪里都行,只要提供一个绝对路径就行。这种方式使用极少,因为移植性太差。
class: 这个位置提供的是mapper接口的全限定接口名,必须带有包名的。
思考:mapper标签的作用是指定SqlMapper.xml文件的路径,指定接口名有什么用呢?
如果你class指定是:com.powernode.mybatis.mapper.CarMapper
那么mybatis框架会自动去com/powernode/mybatis/mapper目录下查找CarMapper.xml文件。
注意:也就是说:如果你采用这种方式,那么你必须保证CarMapper.xml文件和CarMapper接口必须在同一个目录下。并且名字一致。
CarMapper接口-> CarMapper.xml
LogMapper接口-> LogMapper.xml
....
提醒!!!!!!!!!!!!!!!!!!!!!!!
在IDEA的resources目录下新建多重目录的话,必须是这样创建:
com/powernode/mybatis/mapper
不能这样:
com.powernode.mybatis.mapper
模块名:mybatis-006-param
表:t_stu
表中数据:
pojo类:
package com.powernode.mybatis.pojo;
import java.util.Date;
/**
* 学生信息封装类
* @author ShiningSong
* @version 1.0
* @since 1.0
*/
public class Student {
private Long id;
private String name;
private Integer age;
private Double height;
private Character sex;
private Date birth;
@Override
public String toString() {
return "Student{" +
"id=" + id +
", name='" + name + '\'' +
", age=" + age +
", height=" + height +
", sex=" + sex +
", birth=" + birth +
'}';
}
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
public Double getHeight() {
return height;
}
public void setHeight(Double height) {
this.height = height;
}
public Character getSex() {
return sex;
}
public void setSex(Character sex) {
this.sex = sex;
}
public Date getBirth() {
return birth;
}
public void setBirth(Date birth) {
this.birth = birth;
}
public Student() {
}
public Student(Long id, String name, Integer age, Double height, Character sex, Date birth) {
this.id = id;
this.name = name;
this.age = age;
this.height = height;
this.sex = sex;
this.birth = birth;
}
}
简单类型包括:
需求:
根据name查
根据id查
根据birth查
根据sex查
StudentMapper接口
package com.powernode.mybatis.mapper;
import com.powernode.mybatis.pojo.Student;
import java.util.Date;
import java.util.List;
/**
* 学生表映射接口
* @author ShiningSong
* @version 1.0
* @since 1.0
*/
public interface StudentMapper {
/**
* 根据学生名查询学生信息
* @param name
* @return
*/
List<Student> selectByName(String name);
/**
* 根据id查询学生信息
* @param id
* @return
*/
Student selectById(Long id);
/**
* 根据生日birth查找学生信息
* @param birth
* @return
*/
List<Student> selectByBirth(Date birth);
/**
* 根据性别sex查询学生信息
* @param sex
* @return
*/
List<Student> selectBySex(Character sex);
}
StudentMapper.xml
DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.powernode.mybatis.mapper.StudentMapper">
<select id="selectByName" resultType="Student">
select * from t_stu where name=#{name}
select>
<select id="selectById" resultType="Student">
select * from t_stu where id=#{id}
select>
<select id="selectByBirth" resultType="Student">
select * from t_stu where birth=#{birth}
select>
<select id="selectBySex" resultType="Student">
select * from t_stu where sex=#{sex}
select>
mapper>
测试类:
package com.powernode.mybatis.test;
import com.powernode.mybatis.mapper.StudentMapper;
import com.powernode.mybatis.pojo.Student;
import com.powernode.mybatis.utils.SqlSessionUtil;
import org.apache.ibatis.session.SqlSession;
import org.junit.Test;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.List;
/**
* 测试类:测试学生映射方法
*/
public class StudentMapperTest {
@Test
public void testSelectBySex(){
SqlSession sqlSession = SqlSessionUtil.openSession();
StudentMapper mapper = sqlSession.getMapper(StudentMapper.class);
List<Student> students = mapper.selectBySex('男');
students.forEach(student -> System.out.println(student));
sqlSession.close();
}
@Test
public void testSelectByBirth(){
SqlSession sqlSession = SqlSessionUtil.openSession();
StudentMapper mapper = sqlSession.getMapper(StudentMapper.class);
try {
List<Student> students = mapper.selectByBirth(new SimpleDateFormat("yyyy-MM-dd").parse("2022-08-16"));
students.forEach(student -> System.out.println(student));
} catch (ParseException e) {
throw new RuntimeException(e);
}
sqlSession.close();
}
@Test
public void testSelectById(){
SqlSession sqlSession = SqlSessionUtil.openSession();
StudentMapper mapper = sqlSession.getMapper(StudentMapper.class);
Student student = mapper.selectById(2L);
System.out.println(student);
sqlSession.close();
}
@Test
public void testSelectByName(){
SqlSession sqlSession = SqlSessionUtil.openSession();
StudentMapper mapper = sqlSession.getMapper(StudentMapper.class);
List<Student> students = mapper.selectByName("张三");
students.forEach(student -> System.out.println(student));
sqlSession.close();
}
}
执行结果正常。
通过测试得知,简单类型对于mybatis来说都是可以自动类型识别的:
其实SQL映射文件中的配置比较完整的写法是:
<select id="selectByName" resultType="Student" parameterType="java.lang.String">
select * from t_stu where name=#{name}
select>
其中sql语句中的javaType,jdbcType,以及select标签中的parameterType属性,都是用来帮助 mybatis进行类型确定的。不过这些配置多数是可以省略的。因为mybatis它有强大的自动类型推断机制。
如果参数只有一个的话,#{} ⾥面的内容就随便写了。对于 ${} 来说,注意加单引号。
需求:根据name和age查询
StudentMapper接口
/**
* 利用Map根据name和age查询学生信息
* @param paramMap
* @return
*/
List<Student> selectByParaMap(Map<String, Object> paramMap);
StudentMapper.xml
<select id="selectByParaMap" resultType="Student">
select * from t_stu where name=#{nameKey} and age=#{ageKey}
select>
测试类:
@Test
public void testSelectByParaMap(){
SqlSession sqlSession = SqlSessionUtil.openSession();
StudentMapper mapper = sqlSession.getMapper(StudentMapper.class);
Map<String, Object> map = new HashMap<>();
map.put("nameKey", "张三");
map.put("ageKey", 20);
List<Student> students = mapper.selectByParaMap(map);
students.forEach(student -> System.out.println(student));
sqlSession.close();
}
执行结果正常。
这种方式是手动封装Map集合,将每个条件以key和value的形式存放到集合中。然后在使用的时候通过#{map集合的key}来取值。
需求:插入一条Student数据
StudentMapper接口
/**
* 插入学生信息
* @param student
* @return
*/
int insertByPOJO(Student student);
StudentMapper.xml
<insert id="insertByPOJO">
insert into t_stu
values (null, #{name}, #{age}, #{height}, #{birth}, #{sex})
insert>
测试类:
@Test
public void testInsertByPOJO(){
SqlSession sqlSession = SqlSessionUtil.openSession();
StudentMapper mapper = sqlSession.getMapper(StudentMapper.class);
Student student = new Student(null, "李四", 18, 1.70, '男', new Date());
int count = mapper.insertByPOJO(student);
System.out.println(count);
sqlSession.commit();
sqlSession.close();
}
执行结果正常。
这⾥需要注意的是:#{} ⾥面写的是属性名字。这个属性名其本质上是:set/get方法名去掉set/get之后的名字。
需求:通过name和sex查询
StudentMapper接口
/**
* 多参数查询,根据name和sex查询学生信息
* @return
*/
List<Student> selectByMultiParam(String name, Character sex);
StudentMapper.xml
<select id="selectByMultiParam" resultType="Student">
select * from t_stu
where name=#{name} and sex=#{sex}
select>
测试类:
@Test
public void testSelectByMultiParam(){
SqlSession sqlSession = SqlSessionUtil.openSession();
StudentMapper mapper = sqlSession.getMapper(StudentMapper.class);
List<Student> students = mapper.selectByMultiParam("张三", '女');
students.forEach(student -> System.out.println(student));
sqlSession.close();
}
执行结果:
异常信息描述了:name参数找不到,可用的参数包括[arg1, arg0, param1, param2]
修改StudentMapper.xml配置文件:尝试使用[arg1, arg0, param1, param2]去参数
StudentMapper.xml
<select id="selectByMultiParam" resultType="Student">
select * from t_stu
where name=#{arg0} and sex=#{arg1}
select>
执行结果:
通过测试可以看到:
采用param1, param2, … 的方式也可以,甚至可以arg*和param*混用,但是要记住,arg*标记是从0开始,param*标记是从1开始
**实现原理:**实际上在mybatis底层会创建一个map集合,以arg0/param1为key,以方法上的参数为 value,例如以下代码:
Map<String, Object> map = new HashMap<>();
map.put("arg0", name);
map.put("arg1", sex);
map.put("param1", name);
map.put("param2", sex);
// 所以可以这样取值:#{arg0} #{arg1} #{param1} #{param2}
// 其本质就是#{map集合的key}
注意:使用mybatis3.4.2之前的版本时:要用#{0}和#{1}这种形式。
可以不用arg0 arg1 param1 param2吗?这个map集合的key我们自定义可以吗?
需求:根据name和age查询
StudentMapper接口
/**
* 使用@Param注解,根据name和age查询学生信息
* @return
*/
List<Student> selectByParamAnnotation(@Param("name")String name, @Param("age")Integer age);
StudentMapper.xml
<select id="selectByParamAnnotation" resultType="Student">
select * from t_stu
where name=#{name} and age=#{age}
select>
测试类:
@Test
public void testSelectByParamAnnotation(){
SqlSession sqlSession = SqlSessionUtil.openSession();
StudentMapper mapper = sqlSession.getMapper(StudentMapper.class);
List<Student> students = mapper.selectByParamAnnotation("张三", 20);
students.forEach(student -> System.out.println(student));
sqlSession.close();
}
执行结果正常。
核心:@Param(“这⾥填写的其实就是map集合的key”)
当查询的结果,有对应的实体类,并且查询结果只有一条时:
CarMapper接口:
package com.powernode.mybatis.mapper;
import com.powernode.mybatis.pojo.Car;
/**
* 汽车表t_car映射接口
* @author ShiningSong
* @version 1.0
* @since 1.0
*/
public interface CarMapper {
/**
* 根据id查询车辆信息
* @param id
* @return
*/
Car selectById(Long id);
}
CarMapper.xml
DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.powernode.mybatis.mapper.CarMapper">
<select id="selectById" resultType="Car">
select
id,
car_num as carNum,
brand,
guide_price as guidePrice,
produce_time as produceTime,
car_type as carType
from t_car
where id=#{id}
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.apache.ibatis.session.SqlSession;
import org.junit.Test;
public class CarMapperTest {
@Test
public void testSelectById(){
SqlSession sqlSession = SqlSessionUtil.openSession();
CarMapper mapper = sqlSession.getMapper(CarMapper.class);
Car car = mapper.selectById(2L);
System.out.println(car);
sqlSession.close();
}
}
测试结果:
查询结果是一条的话可以使用List集合接收吗?
CarMapper接口
/**
* 根据id查询车辆信息放入List中
* @param id
* @return
*/
List<Car> selectByIdToList(Long id);
CarMapper.xml
<select id="selectByIdToList" resultType="Car">
select
id,
car_num as carNum,
brand,
guide_price as guidePrice,
produce_time as produceTime,
car_type as carType
from t_car
where id=#{id}
select>
测试类:
@Test
public void testSelectByIdToList(){
SqlSession sqlSession = SqlSessionUtil.openSession();
CarMapper mapper = sqlSession.getMapper(CarMapper.class);
List<Car> cars = mapper.selectByIdToList(1L);
System.out.println(cars);
sqlSession.close();
}
执行结果:
当查询的记录条数是多条的时候,必须使用集合接收。如果使用单个实体类接收会出现异常。
CarMapper接口
/**
* 查询所有车辆信息
* @return
*/
List<Car> selectAll();
CarMapper.xml
<select id="selectAll" resultType="Car">
select
id,
car_num as carNum,
brand,
guide_price as guidePrice,
produce_time as produceTime,
car_type as carType
from t_car
select>
测试类:
@Test
public void testSelectAll(){
SqlSession sqlSession = SqlSessionUtil.openSession();
CarMapper mapper = sqlSession.getMapper(CarMapper.class);
List<Car> cars = mapper.selectAll();
cars.forEach(car -> System.out.println(car));
sqlSession.close();
}
执行结果:
如果返回多条记录,采用单个实体类接收会怎样?
CarMapper接口
/**
* 将查询结果返回单个POJO类
* 如果查询结果超过一个会报异常
* @return
*/
Car selectAllToPOJO();
CarMapper.xml
<select id="selectAllToPOJO" resultType="Car">
select
id,
car_num as carNum,
brand,
guide_price as guidePrice,
produce_time as produceTime,
car_type as carType
from t_car
select>
测试类:
@Test
public void testSelectAllToPOJO(){
SqlSession sqlSession = SqlSessionUtil.openSession();
CarMapper mapper = sqlSession.getMapper(CarMapper.class);
Car car = mapper.selectAllToPOJO();
System.out.println(car);
sqlSession.close();
}
执行结果:
当返回的数据,没有合适的实体类对应的话,可以采用Map集合接收。
字段名做key,字段值做value。 查询如果可以保证只有一条数据,则返回一个Map集合即可。
CarMapper接口
/**
* 通过id查询一条记录,返回Map集合
* @param id
* @return
*/
Map<String, Object> selectByIdRetMap(Long id);
resultMap=“map”,这是因为mybatis内置了很多别名。【参见mybatis开发手册】
CarMapper.xml
<select id="selectByIdRetMap" resultType="map">
select
id,
car_num as carNum,
brand,
guide_price as guidePrice,
produce_time as produceTime,
car_type as carType
from t_car
where id=#{id}
select>
测试类:
@Test
public void testSelectByIdRetMap(){
SqlSession sqlSession = SqlSessionUtil.openSession();
CarMapper mapper = sqlSession.getMapper(CarMapper.class);
Map<String, Object> car = mapper.selectByIdRetMap(1L);
System.out.println(car);
sqlSession.close();
}
执行结果:
当然,如果返回一个Map集合,可以将Map集合放到List集合中吗?
反过来,如果返回的不是一条记录,是多条记录的话,只采用单个Map集合接收,这样同样会出现之前的异常:TooManyResultsException
查询结果条数大于等于1条数据,则可以返回一个存储Map集合的List集合。List等同于List
CarMapper接口
/**
* 查询所有车辆信息,返回一个List集合,List集合中存储的是保存车辆信息的Map集合
* @return
*/
List<Map<String, Object>> selectAllRetListMap();
CarMapper.xml
<select id="selectAllRetListMap" resultType="map">
select
id,
car_num as carNum,
brand,
guide_price as guidePrice,
produce_time as produceTime,
car_type as carType
from t_car
select>
测试类:
@Test
public void testSelectAllRetListMap(){
SqlSession sqlSession = SqlSessionUtil.openSession();
CarMapper mapper = sqlSession.getMapper(CarMapper.class);
List<Map<String, Object>> cars = mapper.selectAllRetListMap();
System.out.println(cars);
sqlSession.close();
}
执行结果:
[
{carType=燃油车, carNum=100, guidePrice=41.00, produceTime=2022-09-01, id=1, brand=宝马520Li},
{carType=电车, carNum=101, guidePrice=54.00, produceTime=2022-08-01, id=2, brand=奔驰E300L},
{carType=燃油车, carNum=1003, guidePrice=30.00, produceTime=2000-10-11, id=4, brand=丰田霸道},
......,
{carType=燃油车, carNum=9999, guidePrice=920.00, produceTime=2018-09-10, id=40, brand=劳斯莱斯幻影}
]
拿Car的id做key,以后取出对应的Map集合时更方便。
使用 @MapKey(“id”); 注解实现
CarMapper接口
/**
* 查询所有车辆信息,返回一个Map集合,
* Map集合中存储的是保存车辆信息的Map集合,
* 大Map的key是小map的id,使用@MapKey注解实现
* @return
*/
@MapKey("id")
Map<Long, Map<String, Object>> selectAllRetMap();
CarMapper.xml
<select id="selectAllRetMap" resultType="map">
select
id,
car_num as carNum,
brand,
guide_price as guidePrice,
produce_time as produceTime,
car_type as carType
from t_car
select>
测试类:
@Test
public void testSelectAllRetMap(){
SqlSession sqlSession = SqlSessionUtil.openSession();
CarMapper mapper = sqlSession.getMapper(CarMapper.class);
Map<Long, Map<String, Object>> cars = mapper.selectAllRetMap();
System.out.println(cars);
sqlSession.close();
}
执行结果:
{
1={carType=燃油车, carNum=100, guidePrice=41.00, produceTime=2022-09-01, id=1, brand=宝马520Li},
2={carType=电车, carNum=101, guidePrice=54.00, produceTime=2022-08-01, id=2, brand=奔驰E300L},
4={carType=燃油车, carNum=1003, guidePrice=30.00, produceTime=2000-10-11, id=4, brand=丰田霸道},
......,
40={carType=燃油车, carNum=9999, guidePrice=920.00, produceTime=2018-09-10, id=40, brand=劳斯莱斯幻影}
}
查询结果的列名和java对象的属性名对应不上怎么办?
CarMapper接口
/**
* 查询所有Car,使用resultMap进行结果映射
* @return
*/
List<Car> selectAllByResultMap();
CarMapper.xml
<resultMap id="carResultMap" type="Car">
<id property="id" column="id"/>
<result property="carNum" column="car_num"/>
<result property="brand" column="brand" javaType="String" jdbcType="VARCHAR"/>
<result property="guidePrice" column="guide_price"/>
<result property="produceTime" column="produce_time"/>
<result property="carType" column="car_type"/>
resultMap>
<select id="selectAllByResultMap" resultMap="carResultMap">
select * from t_car
select>
测试类:
@Test
public void testSelectAllByResultMap(){
SqlSession sqlSession = SqlSessionUtil.openSession();
CarMapper mapper = sqlSession.getMapper(CarMapper.class);
List<Car> cars = mapper.selectAllByResultMap();
cars.forEach(car -> System.out.println(car));
sqlSession.close();
}
执行结果正常。
实体类中的属性名 | 数据库表的列名 |
---|---|
carNum | car_num |
guidePrice | guide_price |
produceTime | produce_time |
carType | car_type |
如何启用该功能,在mybatis-config.xml文件中进行配置:
<settings>
<setting name="mapUnderscoreToCamelCase" value="true"/>
settings>
CarMapper接口
/**
* 查询所有车辆信息,启用驼峰命名自动映射
* @return
*/
List<Car> selectAllByMapUnderscoreToCamelCase();
CarMapper.xml
<select id="selectAllByMapUnderscoreToCamelCase" resultType="Car">
select * from t_car
select>
测试类:
@Test
public void testSelectAllByMapUnderscoreToCamelCase(){
SqlSession sqlSession = SqlSessionUtil.openSession();
CarMapper mapper = sqlSession.getMapper(CarMapper.class);
List<Car> cars = mapper.selectAllByMapUnderscoreToCamelCase();
cars.forEach(car -> System.out.println(car));
sqlSession.close();
}
执行结果正常。
需求:查询总记录条数
CarMapper接口
/**
* 获取总记录条数
* @return
*/
Long selectTotal();
CarMapper.xml
<select id="selectTotal" resultType="long">
select count(*) from t_car
select>
测试类:
@Test
public void testSelectTotal(){
SqlSession sqlSession = SqlSessionUtil.openSession();
CarMapper mapper = sqlSession.getMapper(CarMapper.class);
Long count = mapper.selectTotal();
System.out.println("总记录条数为:" + count);
sqlSession.close();
}
执行结果:
有的业务场景,也需要SQL语句进行动态拼接,例如:
delete from t_car where id in(1,2,3,4,5,6,......这⾥的值是动态的,根据用户选择的id不同,值是不同的);
多条件查询
select * from t_car where brand like '丰⽥%' and guide_price > 30 and .....;
创建模块:mybatis-008-dynamic-sql
打包方式:jar
引入依赖:mysql驱动依赖、mybatis依赖、junit依赖、logback依赖
pojo:com.powernode.mybatis.pojo.Car
mapper接口:com.powernode.mybatis.mapper.CarMapper
引入配置文件:mybatis-config.xml、jdbc.properties、logback.xml
mapper配置文件:com/powernode/mybatis/mapper/CarMapper.xml
编写测试类:com.powernode.mybatis.test.CarMapperTest
拷贝工具类:SqlSessionUtil
需求:多条件查询。
可能的条件包括:品牌(brand)、指导价格(guide_price)、汽车类型(car_type)
CarMapper接口
package com.powernode.mybatis.mapper;
import com.powernode.mybatis.pojo.Car;
import org.apache.ibatis.annotations.Param;
import java.util.List;
/**
* 汽车表映射接口
* @author ShiningSong
* @version 1.0
* @since 1.0
*/
public interface CarMapper {
/**
* 多条件查询车辆信息
* 条件包括:品牌(brand)、指导价格(guide_price)、汽车类型(car_type)
* @param brand
* @param guidePrice
* @param carType
* @return
*/
List<Car> selectByMultiCondition(@Param("brand")String brand, @Param("guidePrice")Double guidePrice, @Param("carType")String carType);
}
CarMapper.xml
DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.powernode.mybatis.mapper.CarMapper">
<select id="selectByMultiCondition" resultType="Car">
select * from t_car where
<if test="brand != null and brand != ''">
brand like "%"#{brand}"%"
if>
<if test="guidePrice != null and guidePrice != ''">
and guide_price >= #{guidePrice}
if>
<if test="carType != null and carType != ''">
and car_type = #{carType}
if>
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.apache.ibatis.session.SqlSession;
import org.junit.Test;
import java.util.List;
public class CarMapperTest {
@Test
public void testSelectByMultiCondition(){
SqlSession sqlSession = SqlSessionUtil.openSession();
CarMapper mapper = sqlSession.getMapper(CarMapper.class);
List<Car> cars = mapper.selectByMultiCondition("丰田", 10.0, "燃油车");
cars.forEach(car -> System.out.println(car));
sqlSession.close();
}
}
执行结果:
如果第一个条件为空,剩下两个条件不为空,会是怎样呢?
List<Car> cars = mapper.selectByMultiCondition(null, 10.0, "燃油车");
执行结果:
报错了,SQL语法有问题,where后面出现了and。这该怎么解决呢?
可以where后面添加一个恒成⽴的条件。
CarMapper.xml
<select id="selectByMultiCondition" resultType="Car">
select * from t_car where 1=1
<if test="brand != null and brand != ''">
brand like "%"#{brand}"%"
if>
<if test="guidePrice != null and guidePrice != ''">
and guide_price >= #{guidePrice}
if>
<if test="carType != null and carType != ''">
and car_type = #{carType}
if>
select>
执行结果:
如果三个条件都是空,有影响吗?
List<Car> cars = mapper.selectByMultiCondition("", null, "");
执行结果:
三个条件都不为空呢?
执行结果:
where标签的作用:让where子句更加动态智能。
继续使用if标签中的需求。
CarMapper接口
/**
* 多条件查询车辆信息
* 条件包括:品牌(brand)、指导价格(guide_price)、汽车类型(car_type)
* 使用标签实现
* @param brand
* @param guidePrice
* @param carType
* @return
*/
List<Car> selectByMultiConditionWithWhere(@Param("brand")String brand, @Param("guidePrice")Double guidePrice, @Param("carType")String carType);
CarMapper.xml
<select id="selectByMultiConditionWithWhere" resultType="Car">
select * from t_car
<where>
<if test="brand != null and brand != ''">
and brand like "%"#{brand}"%"
if>
<if test="guidePrice != null and guidePrice != ''">
and guide_price >= #{guidePrice}
if>
<if test="carType != null and carType != ''">
and car_type = #{carType}
if>
where>
select>
测试类:
@Test
public void testSelectByMultiConditionWithWhere() {
SqlSession sqlSession = SqlSessionUtil.openSession();
CarMapper mapper = sqlSession.getMapper(CarMapper.class);
List<Car> cars = mapper.selectByMultiConditionWithWhere("丰田", 10.0, "燃油车");
cars.forEach(car -> System.out.println(car));
sqlSession.close();
}
执行结果:
如果所有条件都是空呢?
测试类:
List<Car> cars = mapper.selectByMultiCondition("", null, "");
执行结果:
它可以自动去掉前面多余的and,那可以自动去掉前面多余的or吗?
测试类:
List<Car> cars = mapper.selectByMultiConditionWithWhere("丰田", 10.0, "燃油车");
CarMapper.xml
<select id="selectByMultiConditionWithWhere" resultType="Car">
select * from t_car
<where>
<if test="brand != null and brand != ''">
or brand like "%"#{brand}"%"
if>
<if test="guidePrice != null and guidePrice != ''">
and guide_price >= #{guidePrice}
if>
<if test="carType != null and carType != ''">
and car_type = #{carType}
if>
where>
select>
测试结果:
它可以自动去掉前面多余的and,那可以自动去掉后面多余的and吗?
测试类:
// 设置最后一个参数为空
List<Car> cars = mapper.selectByMultiConditionWithWhere("丰田", 10.0, "");
CarMapper.xml
<select id="selectByMultiConditionWithWhere" resultType="Car">
select * from t_car
<where>
<if test="brand != null and brand != ''">
brand like "%"#{brand}"%" and
if>
<if test="guidePrice != null and guidePrice != ''">
guide_price >= #{guidePrice} and
if>
<if test="carType != null and carType != ''">
car_type = #{carType}
if>
where>
select>
测试结果:
很显然,后面多余的and是不会被去除的。
trim标签的属性:
CarMapper接口
/**
* 多条件查询车辆信息
* 条件包括:品牌(brand)、指导价格(guide_price)、汽车类型(car_type)
* 使用标签实现
* @param brand
* @param guidePrice
* @param carType
* @return
*/
List<Car> selectByMultiConditionWithTrim(@Param("brand")String brand, @Param("guidePrice")Double guidePrice, @Param("carType")String carType);
CarMapper.xml
<select id="selectByMultiConditionWithTrim" resultType="Car">
select * from t_car
<trim prefix="where" suffixOverrides="and|or">
<if test="brand != null and brand != ''">
brand like "%"#{brand}"%" and
if>
<if test="guidePrice != null and guidePrice != ''">
guide_price >= #{guidePrice} and
if>
<if test="carType != null and carType != ''">
car_type = #{carType}
if>
trim>
select>
测试类:
@Test
public void testSelectByMultiConditionWithTrim() {
SqlSession sqlSession = SqlSessionUtil.openSession();
CarMapper mapper = sqlSession.getMapper(CarMapper.class);
List<Car> cars = mapper.selectByMultiConditionWithTrim("丰田", 10.0, "");
cars.forEach(car -> System.out.println(car));
sqlSession.close();
}
测试结果:
主要使用在update语句当中,用来生成set关键字,同时去掉最后多余的“,”
比如我们只更新提交的不为空的字段,如果提交的数据是空或者"",那么这个字段我们将不更新。
CarMapper接口
/**
* 使用标签更新车辆信息
* @param car
* @return
*/
int updateWithSet(Car car);
CarMapper.xml
<update id="updateWithSet">
update t_car
<set>
<if test="carNum != null and carNum != ''">
car_num = #{carNum},
if>
<if test="brand != null and brand != ''">
brand = #{brand},
if>
<if test="guidePrice != null and guidePrice != ''">
guide_price = #{guidePrice},
if>
<if test="produceTime != null and produceTime != ''">
produce_time = #{produceTime},
if>
<if test="carType != null and carType != ''">
car_type = #{carType},
if>
set>
where id = #{id}
update>
测试类:
@Test
public void testUpdateWithSet(){
SqlSession sqlSession = SqlSessionUtil.openSession();
CarMapper mapper = sqlSession.getMapper(CarMapper.class);
Car car = new Car(12L, "1010", "特斯拉model3", 26.0, "", null);
int count = mapper.updateWithSet(car);
System.out.println(count);
sqlSession.commit();
sqlSession.close();
}
测试结果:
这三个标签是在一起使用的:
<choose>
<when>when>
<when>when>
<when>when>
<otherwise>otherwise>
choose>
等同于:
if(){
}else if(){
}else if(){
}else if(){
}else{
}
只有一个分支会被选择!!!!
需求:先根据品牌查询,如果没有提供品牌,再根据指导价格查询,如果没有提供指导价格,就根据生 产日期查询。
CarMapper接口
/**
* 使用、、标签查询车辆信息
* 先根据品牌查询,如果没有提供品牌,再根据指导价格查询,如果没有提供指导价格,就根据生产日期查询。
* @param brand
* @param guidePrice
* @param produceTime
* @return
*/
List<Car> selectByChoose(@Param("brand")String brand, @Param("guidePrice")Double guidePrice, @Param("produceTime")String produceTime);
CarMapper.xml
<select id="selectByChoose" resultType="Car">
select * from t_car
<where>
<choose>
<when test="brand != null and brand != ''">
brand like "%"#{brand}"%"
when>
<when test="guidePrice != null and guidePrice != ''">
guide_price >= #{guidePrice}
when>
<otherwise>
produce_time >= #{produceTime}
otherwise>
choose>
where>
select>
测试类:
@Test
public void testSelectByChoose(){
SqlSession sqlSession = SqlSessionUtil.openSession();
CarMapper mapper = sqlSession.getMapper(CarMapper.class);
List<Car> cars = mapper.selectByChoose("丰田霸道", 20.0, "2000-10-10");
cars.forEach(car -> System.out.println(car));
sqlSession.close();
}
执行结果:
测试类:
List<Car> cars = mapper.selectByChoose("", 20.0, "2000-10-10");
执行结果:
测试类:
List<Car> cars = mapper.selectByChoose("", null, "2000-10-10");
执行结果:
测试类:
List<Car> cars = mapper.selectByChoose("", null, "");
执行结果:
循环数组或集合,动态生成sql,比如这样的SQL:
delete from t_car where id in (1, 2, 3);
delete from t_car where id=1 or id=2 or id=3;
insert into t_car values
(null, '1001', '凯美瑞', 35.0, '2010-10-11', '燃油车')
(null, '1002', '比亚迪唐', 31.0, '2020-11-11', '新能源')
(null, '1003', '比亚迪宋', 32.0, '2020-10-11', '新能源')
用in来删除
CarMapper接口
/**
* 根据id批量删除车辆信息,用in
* 使用标签
* @param ids
* @return
*/
int deleteBatchByForeachWithIn(@Param("ids")Long[] ids);
CarMapper.xml
<delete id="deleteBatchByForeachWithIn">
delete from t_car where id in
<foreach collection="ids" item="id" separator="," open="(" close=")">
#{id}
foreach>
delete>
测试类:
@Test
public void testDeleteBatchByForeachWithIn(){
SqlSession sqlSession = SqlSessionUtil.openSession();
CarMapper mapper = sqlSession.getMapper(CarMapper.class);
int count = mapper.deleteBatchByForeachWithIn(new Long[]{5L, 6L, 7L});
System.out.println(count);
sqlSession.commit();
sqlSession.close();
}
执行结果:
用or来删除
CarMapper接口
/**
* 根据id批量删除车辆信息,用or
* 使用标签
* @param ids
* @return
*/
int deleteBatchByForeachWithOr(@Param("ids")Long[] ids);
CarMapper.xml
<delete id="deleteBatchByForeachWithOr">
delete from t_car where
<foreach collection="ids" item="id" separator="or">
id = #{id}
foreach>
delete>
测试类:
@Test
public void testDeleteBatchByForeachWithOr(){
SqlSession sqlSession = SqlSessionUtil.openSession();
CarMapper mapper = sqlSession.getMapper(CarMapper.class);
int count = mapper.deleteBatchByForeachWithOr(new Long[]{5L, 6L, 7L});
System.out.println(count);
sqlSession.commit();
sqlSession.close();
}
执行结果:
CarMapper接口
/**
* 批量添加车辆信息
* 使用标签
* @param cars
* @return
*/
int insertBatchByForeach(@Param("cars")List<Car> cars);
CarMapper.xml
<insert id="insertBatchByForeach">
insert into t_car values
<foreach collection="cars" item="car" separator=",">
(null, #{car.carNum}, #{car.brand}, #{car.guidePrice}, #{car.produceTime}, #{car.carType})
foreach>
insert>
测试类:
@Test
public void testInsertBatchByForeach(){
SqlSession sqlSession = SqlSessionUtil.openSession();
CarMapper mapper = sqlSession.getMapper(CarMapper.class);
Car car1 = new Car(null, "1001", "凯美瑞", 35.0, "2010-10-11", "燃油车");
Car car2 = new Car(null, "1002", "比亚迪唐", 31.0, "2020-11-11", "新能源");
Car car3 = new Car(null, "1003", "比亚迪宋", 32.0, "2020-10-11", "新能源");
List<Car> cars = Arrays.asList(car1, car2, car3);
int count = mapper.insertBatchByForeach(cars);
System.out.println(count);
sqlSession.commit();
sqlSession.close();
}
执行结果:
sql标签用来声明sql片段
include标签用来将声明的sql片段包含到某个sql语句当中
作用:代码复用。易维护。
CarMapper接口
/**
* 查询所有汽车信息
* @return
*/
List<Car> selectAll();
CarMapper.xml
<sql id="carCols">
id,
car_num as carNum,
brand,
guide_price as guidePrice,
produce_time as produceTime,
car_type as carType
sql>
<select id="selectAll" resultType="com.powernode.mybatis.pojo.Car">
select
<include refid="carCols">include>
from t_car
select>
测试类:
@Test
public void testSelectAll(){
SqlSession sqlSession = SqlSessionUtil.openSession();
CarMapper mapper = sqlSession.getMapper(CarMapper.class);
List<Car> cars = mapper.selectAll();
cars.forEach(car -> System.out.println(car));
sqlSession.close();
}
执行结果:
模块名:mybatis-009-advanced-mapping
打包方式:jar
依赖:mybatis依赖、mysql驱动依赖、junit依赖、logback依赖
配置文件:mybatis-config.xml、logback.xml、jdbc.properties
拷贝工具类:SqlSessionUtil
准备数据库表:一个班级对应多个学生。
创建pojo:Student、Clazz
Student.java
package com.powernode.mybatis.pojo;
/**
* 学生类
* @author ShiningSong
* @version 1.0
* @since 1.0
*/
public class Student {
private Long sid;
private String sname;
......
}
Clazz.java
package com.powernode.mybatis.pojo;
/**
* 班级类
* @author ShiningSong
* @version 1.0
* @since 1.0
*/
public class Clazz {
private Long cid;
private String cname;
......
}
创建mapper接口:StudentMapper、ClazzMapper
创建mapper映射文件:StudentMapper.xml、ClazzMapper.xml
多种方式,常见的包括三种:
pojo类Student中添加一个属性:Clazz clazz; 表示学生关联的班级对象。
package com.powernode.mybatis.pojo;
/**
* 学生类
* @author ShiningSong
* @version 1.0
* @since 1.0
*/
public class Student {
private Long sid;
private String sname;
private Clazz clazz;
@Override
public String toString() {
return "Student{" +
"sid=" + sid +
", sname='" + sname + '\'' +
", clazz=" + clazz +
'}';
}
public Long getSid() {
return sid;
}
public void setSid(Long sid) {
this.sid = sid;
}
public String getSname() {
return sname;
}
public void setSname(String sname) {
this.sname = sname;
}
public Clazz getClazz() {
return clazz;
}
public void setClazz(Clazz clazz) {
this.clazz = clazz;
}
public Student(Long sid, String sname, Clazz clazz) {
this.sid = sid;
this.sname = sname;
this.clazz = clazz;
}
public Student() {
}
}
StudentMapper接口
package com.powernode.mybatis.mapper;
import com.powernode.mybatis.pojo.Student;
/**
* 学生表映射接口
* @author ShiningSong
* @version 1.0
* @since 1.0
*/
public interface StudentMapper {
/**
* 通过sid查询学生信息
* @param sid
* @return
*/
Student selectBySid(Long sid);
}
StudentMapper.xml
DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.powernode.mybatis.mapper.StudentMapper">
<resultMap id="studentResultMap" type="Student">
<id property="sid" column="sid"/>
<result property="sname" column="sname"/>
<result property="clazz.cid" column="cid"/>
<result property="clazz.cname" column="cname"/>
resultMap>
<select id="selectBySid" resultMap="studentResultMap">
select s.*, c.*
from t_student s
left join t_clazz c
on s.cid = c.cid
where sid = #{sid}
select>
mapper>
测试类:
package com.powernode.mybatis.test;
import com.mysql.cj.xdevapi.SqlUpdateResult;
import com.powernode.mybatis.mapper.StudentMapper;
import com.powernode.mybatis.pojo.Student;
import com.powernode.mybatis.utils.SqlSessionUtil;
import org.apache.ibatis.session.SqlSession;
import org.junit.Test;
public class AdvancedMappingTest {
@Test
public void testSelectBySid(){
SqlSession sqlSession = SqlSessionUtil.openSession();
StudentMapper mapper = sqlSession.getMapper(StudentMapper.class);
Student student = mapper.selectBySid(2L);
System.out.println(student);
sqlSession.close();
}
}
执行结果:
其他位置都不需要修改,只需要修改resultMap中的配置:association即可。
StudentMapper.xml
<resultMap id="studentResultMap" type="Student">
<id property="sid" column="sid"/>
<result property="sname" column="sname"/>
<association property="clazz" javaType="Clazz">
<id property="cid" column="cid"/>
<result property="cname" column="cname"/>
association>
resultMap>
association翻译为:关联。
学生对象关联一个班级对象。
其他位置不需要修改,只需要修改以及添加以下三处:
ClazzMapper接口
package com.powernode.mybatis.mapper;
import com.powernode.mybatis.pojo.Clazz;
/**
* 班级表映射接口
* @author ShiningSong
* @version 1.0
* @since 1.0
*/
public interface ClazzMapper {
/**
* 根据cid查询班级信息
* @param cid
* @return
*/
Clazz selectByCid(Long cid);
}
ClazzMapper.xml
DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.powernode.mybatis.mapper.ClazzMapper">
<select id="selectByCid" resultType="Clazz">
select * from t_clazz where cid = #{cid}
select>
mapper>
StudentMapper.xml
<resultMap id="studentResultMap" type="Student">
<id property="sid" column="sid"/>
<result property="sname" column="sname"/>
<association property="clazz"
select="com.powernode.mybatis.mapper.ClazzMapper.selectByCid"
column="cid"/>
resultMap>
<select id="selectBySid" resultMap="studentResultMap">
select s.* from t_student s where sid=#{sid}
select>
执行结果:可以很明显看到先后有两条sql语句执行
分步优点:
要想支持延迟加载,非常简单,只需要在association标签中添加fetchType="lazy"即可。
修改StudentMapper.xml文件:
StudentMapper.xml
<resultMap id="studentResultMap" type="Student">
<id property="sid" column="sid"/>
<result property="sname" column="sname"/>
<association property="clazz"
select="com.powernode.mybatis.mapper.ClazzMapper.selectByCid"
column="cid"
fetchType="lazy"/>
resultMap>
测试类:
@Test
public void testSelectBySid(){
SqlSession sqlSession = SqlSessionUtil.openSession();
StudentMapper mapper = sqlSession.getMapper(StudentMapper.class);
Student student = mapper.selectBySid(2L);
// System.out.println(student);
// 只获取学生姓名
System.out.println("学生姓名:" + student.getSname());
sqlSession.close();
}
执行结果:
如果后续需要使用到学生所在班级的名称,这个时候才会执行关联的sql语句,修改测试程序:
@Test
public void testSelectBySid(){
SqlSession sqlSession = SqlSessionUtil.openSession();
StudentMapper mapper = sqlSession.getMapper(StudentMapper.class);
Student student = mapper.selectBySid(2L);
// System.out.println(student);
// 只获取学生姓名
System.out.println("学生姓名:" + student.getSname());
// 到这里想要获取班级名字
System.out.println("该学生的班级名称:" + student.getClazz().getCname());
sqlSession.close();
}
通过以上的执行结果可以看到,只有当使用到班级名称之后,才会执行关联的sql语句,这就是延迟加载。
在mybatis中如何开启全局的延迟加载呢?需要setting配置,如下:
mybatis-config.xml
<settings>
<setting name="lazyLoadingEnabled" value="true"/>
settings>
把fetchType="lazy"去掉。
测试类:
@Test
public void testSelectBySid(){
SqlSession sqlSession = SqlSessionUtil.openSession();
StudentMapper mapper = sqlSession.getMapper(StudentMapper.class);
Student student = mapper.selectBySid(2L);
// System.out.println(student);
// 只获取学生姓名
System.out.println("学生姓名:" + student.getSname());
// 到这里想要获取班级名字
System.out.println("该学生的班级名称:" + student.getClazz().getCname());
sqlSession.close();
}
执行结果:
通过以上的测试可以看出,我们已经开启了全局延迟加载策略。
开启全局延迟加载之后,所有的sql都会支持延迟加载,如果某个sql你不希望它支持延迟加载怎么办呢?
StudentMapper.xml
<resultMap id="studentResultMap" type="Student">
<id property="sid" column="sid"/>
<result property="sname" column="sname"/>
<association property="clazz"
select="com.powernode.mybatis.mapper.ClazzMapper.selectByCid"
column="cid"
fetchType="eager"/>
resultMap>
执行结果:
这样的话,针对某个特定的sql,你就关闭了延迟加载机制。
后期我们要不要开启延迟加载机制,主要看实际的业务需求是怎样的。
一对多的实现,通常是在一的一方中有List集合属性。
在Clazz类中添加List stus; 属性。
Clazz.java
package com.powernode.mybatis.pojo;
import java.util.List;
/**
* 班级类
* @author ShiningSong
* @version 1.0
* @since 1.0
*/
public class Clazz {
private Long cid;
private String cname;
private List<Student> students;
@Override
public String toString() {
return "Clazz{" +
"cid=" + cid +
", cname='" + cname + '\'' +
", students=" + students +
'}';
}
public Long getCid() {
return cid;
}
public void setCid(Long cid) {
this.cid = cid;
}
public String getCname() {
return cname;
}
public void setCname(String cname) {
this.cname = cname;
}
public List<Student> getStudents() {
return students;
}
public void setStudents(List<Student> students) {
this.students = students;
}
public Clazz(Long cid, String cname, List<Student> students) {
this.cid = cid;
this.cname = cname;
this.students = students;
}
public Clazz() {
}
}
一对多的实现通常包括两种实现方式:
ClazzMapper接口
/**
* 根据cid查询班级信息
* 同时也要查询班级的学生信息
* @param cid
* @return
*/
Clazz selectClazzAndStudentsByCid(Long cid);
ClazzMapper.xml
<resultMap id="clazzResultMap" type="Clazz">
<id property="cid" column="cid"/>
<result property="cname" column="cname"/>
<collection property="students" ofType="Student">
<id property="sid" column="sid"/>
<result property="sname" column="sname"/>
collection>
resultMap>
<select id="selectClazzAndStudentsByCid" resultMap="clazzResultMap">
select *
from t_clazz c
left join t_student s
on c.cid = s.cid
where c.cid = #{cid}
select>
测试类:
@Test
public void testSelectClazzAndStudentsByCid(){
SqlSession sqlSession = SqlSessionUtil.openSession();
ClazzMapper mapper = sqlSession.getMapper(ClazzMapper.class);
Clazz clazz = mapper.selectClazzAndStudentsByCid(1001L);
System.out.println(clazz);
sqlSession.close();
}
执行结果:
Clazz{cid=1001,
cname='高三1班',
students=[
Student{sid=1, sname='张三', clazz=null},
Student{sid=2, sname='李四', clazz=null},
Student{sid=3, sname='王五', clazz=null}
]
}
修改以下三个位置即可:
StudentMapper接口
/**
* 根据cid查询学生信息返回List集合
* @param cid
* @return
*/
List<Student> selectByCid(Long cid);
StudentMapper.xml
<select id="selectByCid" resultType="Student">
select * from t_student where cid = #{cid}
select>
ClazzMapper.xml
<resultMap id="clazzResultMap" type="Clazz">
<id property="cid" column="cid"/>
<result property="cname" column="cname"/>
<collection property="students"
select="com.powernode.mybatis.mapper.StudentMapper.selectByCid"
column="cid"/>
resultMap>
<select id="selectClazzAndStudentsByCid" resultMap="clazzResultMap">
select * from t_clazz c where c.cid = #{cid}
select>
测试类:
@Test
public void testSelectClazzAndStudentsByCid(){
SqlSession sqlSession = SqlSessionUtil.openSession();
ClazzMapper mapper = sqlSession.getMapper(ClazzMapper.class);
Clazz clazz = mapper.selectClazzAndStudentsByCid(1001L);
System.out.println(clazz);
sqlSession.close();
}
执行结果:
一级缓存默认是开启的。不需要做任何配置。
原理:只要使用同一个SqlSession对象执行同一条SQL语句,就会走缓存。
模块名:mybatis-010-cache
CarMapper接口
package com.powernode.mybatis.mapper;
import com.powernode.mybatis.pojo.Car;
/**
* 汽车表映射接口
* @author ShiningSong
* @version 1.0
* @since 1.0
*/
public interface CarMapper {
/**
* 根据id获取Car信息
* @return
*/
Car selectById(Long id);
}
CarMapper.xml
DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.powernode.mybatis.mapper.CarMapper">
<select id="selectById" resultType="Car">
select * from t_car where id=#{id}
select>
mapper>
测试类:
package com.powernode.mybatis.test;
import com.powernode.mybatis.mapper.CarMapper;
import com.powernode.mybatis.pojo.Car;
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
import org.junit.Test;
import java.io.IOException;
public class CacheTest {
@Test
public void testSelectById() throws IOException {
// 不能使用工具类
SqlSessionFactoryBuilder sqlSessionFactoryBuilder = new SqlSessionFactoryBuilder();
SqlSessionFactory sqlSessionFactory = sqlSessionFactoryBuilder.build(Resources.getResourceAsStream("mybatis-config.xml"));
SqlSession sqlSession1 = sqlSessionFactory.openSession();
CarMapper mapper1 = sqlSession1.getMapper(CarMapper.class);
CarMapper mapper2 = sqlSession1.getMapper(CarMapper.class);
Car car1 = mapper1.selectById(12L);
System.out.println(car1);
Car car2 = mapper2.selectById(12L);
System.out.println(car2);
SqlSession sqlSession2 = sqlSessionFactory.openSession();
CarMapper mapper3 = sqlSession2.getMapper(CarMapper.class);
CarMapper mapper4 = sqlSession2.getMapper(CarMapper.class);
Car car3 = mapper3.selectById(12L);
System.out.println(car3);
Car car4 = mapper4.selectById(12L);
System.out.println(car4);
sqlSession1.close();
sqlSession2.close();
}
}
执行结果:
什么情况下不走缓存?
一级缓存失效情况包括两种:
Car car1 = mapper1.selectById(12L);
System.out.println(car1);
// 手动清空
sqlSession1.clearCache();
Car car2 = mapper2.selectById(12L);
System.out.println(car2);
执行结果:
CarMapper接口
/**
* 插入账户信息
* 任务:在一级缓存之间做一个CUD操作
*/
void insertAccount();
CarMapper.xml
<insert id="insertAccount">
insert into t_act values (3, 'act003', 10000)
insert>
测试类:
Car car1 = mapper1.selectById(12L);
System.out.println(car1);
// 插入一次insert操作(对其他表也行)
mapper1.insertAccount();
Car car2 = mapper2.selectById(12L);
System.out.println(car2);
执行结果:
二级缓存的范围是SqlSessionFactory。
使用二级缓存需要具备以下几个条件:
测试二级缓存:
Car.java
package com.powernode.mybatis.pojo;
import java.io.Serializable;
/**
* 封装汽车的相关信息的pojo类,普通的java类
* @author ShiningSong
* @version 1.1
* @since 1.0
*/
public class Car implements Serializable {
// 实现Serializable接口
......
}
CarMapper.xml
<cache/>
测试类:
@Test
public void testSelectById2() throws IOException {
// 不能使用工具类
SqlSessionFactoryBuilder sqlSessionFactoryBuilder = new SqlSessionFactoryBuilder();
SqlSessionFactory sqlSessionFactory = sqlSessionFactoryBuilder.build(Resources.getResourceAsStream("mybatis-config.xml"));
SqlSession sqlSession1 = sqlSessionFactory.openSession();
CarMapper mapper1 = sqlSession1.getMapper(CarMapper.class);
Car car1 = mapper1.selectById(12L);
System.out.println(car1);
// 关键一步
sqlSession1.close();
SqlSession sqlSession2 = sqlSessionFactory.openSession();
CarMapper mapper3 = sqlSession2.getMapper(CarMapper.class);
Car car3 = mapper3.selectById(12L);
System.out.println(car3);
sqlSession2.close();
}
执行结果:
二级缓存的失效:只要两次查询之间出现了增删改操作。二级缓存就会失效。【一级缓存也会失效】
集成EhCache是为了代替mybatis自带的二级缓存。一级缓存是无法替代的。
mybatis对外提供了接口,也可以集成第三方的缓存组件。比如EhCache、Memcache等。都可以。
EhCache是Java写的。Memcache是C语⾔写的。所以mybatis集成EhCache较为常见,按照以下步骤操 作,就可以完成集成:
<dependency>
<groupId>org.mybatis.cachesgroupId>
<artifactId>mybatis-ehcacheartifactId>
<version>1.2.2version>
dependency>
<dependency>
<groupId>ch.qos.logbackgroupId>
<artifactId>logback-classicartifactId>
<version>1.2.11version>
dependency>
<ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="http://ehcache.org/ehcache.xsd"
updateCheck="false">
<diskStore path="e:/ehcache"/>
<defaultCache eternal="false" maxElementsInMemory="1000" overflowToDisk="false" diskPersistent="false" timeToIdleSeconds="0" timeToLiveSeconds="600" memoryStoreEvictionPolicy="LRU"/>
ehcache>
CarMapper.xml
<cache type="org.mybatis.caches.ehcache.EhcacheCache" />
测试类:
@Test
public void testSelectById2() throws IOException {
SqlSessionFactoryBuilder sqlSessionFactoryBuilder = new SqlSessionFactoryBuilder();
SqlSessionFactory sqlSessionFactory = sqlSessionFactoryBuilder.build(Resources.getResourceAsStream("mybatis-config.xml"));
SqlSession sqlSession1 = sqlSessionFactory.openSession();
CarMapper mapper1 = sqlSession1.getMapper(CarMapper.class);
Car car1 = mapper1.selectById(12L);
System.out.println(car1);
sqlSession1.close();
SqlSession sqlSession2 = sqlSessionFactory.openSession();
CarMapper mapper3 = sqlSession2.getMapper(CarMapper.class);
Car car3 = mapper3.selectById(12L);
System.out.println(car3);
sqlSession2.close();
}
执行结果:
<build>
<plugins>
<plugin>
<groupId>org.mybatis.generatorgroupId>
<artifactId>mybatis-generator-maven-pluginartifactId>
<version>1.4.1version>
<configuration>
<overwrite>trueoverwrite>
configuration>
<dependencies>
<dependency>
<groupId>mysqlgroupId>
<artifactId>mysql-connector-javaartifactId>
<version>8.0.30version>
dependency>
dependencies>
plugin>
plugins>
build>
该文件名必须叫做:generatorConfig.xml
该文件必须放在类的根路径下。
DOCTYPE generatorConfiguration
PUBLIC "-//mybatis.org//DTD MyBatis Generator Configuration 1.0//EN"
"http://mybatis.org/dtd/mybatis-generator-config_1_0.dtd">
<generatorConfiguration>
<context id="DB2Tables" targetRuntime="MyBatis3">
<plugin type="org.mybatis.generator.plugins.UnmergeableXmlMappersPlugin"/>
<commentGenerator>
<property name="suppressDate" value="true"/>
<property name="suppressAllComments" value="true"/>
commentGenerator>
<jdbcConnection driverClass="com.mysql.cj.jdbc.Driver"
connectionURL="jdbc:mysql://localhost:3306/powernode"
userId="root"
password="root">
jdbcConnection>
<javaModelGenerator targetPackage="com.powernode.mybatis.pojo" targetProject="src/main/java">
<property name="enableSubPackages" value="true"/>
<property name="trimStrings" value="true"/>
javaModelGenerator>
<sqlMapGenerator targetPackage="com.powernode.mybatis.mapper" targetProject="src/main/resources">
<property name="enableSubPackages" value="true"/>
sqlMapGenerator>
<javaClientGenerator
type="xmlMapper"
targetPackage="com.powernode.mybatis.mapper"
targetProject="src/main/java">
<property name="enableSubPackages" value="true"/>
javaClientGenerator>
<table tableName="t_car" domainObjectName="Car"/>
context>
generatorConfiguration>
package com.powernode.mybatis.test;
import com.powernode.mybatis.mapper.CarMapper;
import com.powernode.mybatis.pojo.Car;
import com.powernode.mybatis.pojo.CarExample;
import com.powernode.mybatis.utils.SqlSessionUtil;
import org.apache.ibatis.session.SqlSession;
import org.junit.Test;
import java.math.BigDecimal;
import java.util.List;
public class GeneratorCompleteTest {
// CarExample类负责封装查询条件的。
@Test
public void testSelect(){
SqlSession sqlSession = SqlSessionUtil.openSession();
CarMapper mapper = sqlSession.getMapper(CarMapper.class);
// 执行查询
// 1. 查询一个
Car car = mapper.selectByPrimaryKey(12L);
System.out.println(car);
// 2. 查询所有(selectByExample,根据条件查询,如果条件是null表示没有条件。)
List<Car> cars = mapper.selectByExample(null);
cars.forEach(car1 -> System.out.println(car1));
System.out.println("=========================================");
// 3. 按照条件进行查询
// QBC 风格:Query By Criteria 一种查询方式,比较面向对象,看不到sql语句。
// 封装条件,通过CarExample对象来封装查询条件
CarExample carExample = new CarExample();
// 调用carExample.createCriteria()方法来创建查询条件
carExample.createCriteria()
.andBrandLike("帕萨特")
.andGuidePriceGreaterThan(new BigDecimal(20.0));
// 添加or
carExample.or().andCarTypeEqualTo("燃油车");
// 执行查询
List<Car> cars2 = mapper.selectByExample(carExample);
cars2.forEach(car2 -> System.out.println(car2));
sqlSession.close();
}
}
select
*
from
tableName .......
limit
(pageNum - 1) * pageSize, pageSize
CarMapper接口
package com.powernode.mybatis.mapper;
import com.powernode.mybatis.pojo.Car;
import org.apache.ibatis.annotations.Param;
import java.util.List;
/**
* 汽车表映射接口
*/
public interface CarMapper {
/**
* 通过分页的方式获取汽车列表信息
* @param startIndex 页码
* @param pageSize 每页显示记录条数
* @return
*/
List<Car> selectAllPage(@Param("startIndex")Integer startIndex, @Param("pageSize")Integer pageSize);
}
CarMapper.xml
DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.powernode.mybatis.mapper.CarMapper">
<select id="selectAllPage" resultType="Car">
select * from t_car
limit #{startIndex}, #{pageSize}
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.apache.ibatis.session.SqlSession;
import org.junit.Test;
import java.util.List;
public class PageTest {
@Test
public void testSelectAllPage(){
SqlSession sqlSession = SqlSessionUtil.openSession();
CarMapper mapper = sqlSession.getMapper(CarMapper.class);
// 页码
Integer pageNum = 2;
// 每页显示记录条数
Integer pageSize = 3;
// 起始下标
Integer startIndex = (pageNum - 1) * pageSize;
List<Car> cars = mapper.selectAllPage(startIndex, pageSize);
cars.forEach(car -> System.out.println(car));
sqlSession.close();
}
}
执行结果:
<dependency>
<groupId>com.github.pagehelpergroupId>
<artifactId>pagehelperartifactId>
<version>5.3.1version>
dependency>
<plugins>
<plugin interceptor="com.github.pagehelper.PageInterceptor">plugin>
plugins>
CarMapper接口
/**
* 查询所有车辆信息
* @return
*/
List<Car> selectAll();
CarMapper.xml
<select id="selectAll" resultType="Car">
select * from t_car
select>
测试类:
@Test
public void testSelectAll(){
SqlSession sqlSession = SqlSessionUtil.openSession();
CarMapper mapper = sqlSession.getMapper(CarMapper.class);
// 开启分页
PageHelper.startPage(2,3);
// 执行查询语句
List<Car> cars = mapper.selectAll();
// 获取分页信息对象
PageInfo<Car> pageInfo = new PageInfo<>(cars, 5);
System.out.println(pageInfo);
sqlSession.close();
}
执行结果:
PageInfo{
pageNum=2, pageSize=3, size=3, startRow=4, endRow=6, total=24, pages=8,
list=Page{count=true, pageNum=2, pageSize=3, startRow=3, endRow=6, total=24, pages=8, reasonable=false, pageSizeZero=false}
[
Car{id=8, carNum='1003', brand='丰田霸道', guidePrice=30.0, produceTime='2000-10-11', carType='燃油车'},
Car{id=9, carNum='102', brand='比亚迪汉', guidePrice=30.23, produceTime='2018-09-10', carType='电车'},
Car{id=10, carNum='1003', brand='丰田霸道', guidePrice=30.0, produceTime='2000-10-11', carType='燃油车'}
],
prePage=1, nextPage=3, isFirstPage=false, isLastPage=false, hasPreviousPage=true, hasNextPage=true,
navigatePages=5, navigateFirstPage=1, navigateLastPage=5, navigatepageNums=[1, 2, 3, 4, 5]
}
使用注解来映射简单语句会使代码显得更加简洁,但对于稍微复杂一点的语句,Java 注解不仅⼒不从心,还会让你本就复杂的 SQL 语句更加混乱不堪。 因此,如果你需要做一些很复杂的操作,最好用 XML 来映射语句。
使用注解编写复杂的SQL是这样的:
原则:简单sql可以注解。复杂sql使用xml。
模块名:mybatis-013-annotation
打包方式:jar
依赖:mybatis,mysql驱动,junit,logback
配置文件:jdbc.properties、mybatis-config.xml、logback.xml
pojo:com.powernode.mybatis.pojo.Car
mapper接口:com.powernode.mybatis.mapper.CarMapper
CarMapper接口
/**
* 使用@Insert()注解插入车辆信息
* @return
*/
@Insert(value = "insert into t_car values(null, #{carNum}, #{brand}, #{guidePrice}, #{produceTime}, #{carType})")
int insert(Car car);
测试类:
@Test
public void testInsert(){
SqlSession sqlSession = SqlSessionUtil.openSession();
CarMapper mapper = sqlSession.getMapper(CarMapper.class);
Car car = new Car(null, "1112", "卡罗拉", 30.0, "2000-10-10", "燃油车");
int count = mapper.insert(car);
System.out.println("插入了几条数据:" + count);
sqlSession.commit();
sqlSession.close();
}
CarMapper接口
/**
* 使用@Delete()注解删除车辆信息
* @param id
* @return
*/
@Delete("delete from t_car where id = #{id}")
int deleteById(Long id);
测试类:
@Test
public void testDeleteById(){
SqlSession sqlSession = SqlSessionUtil.openSession();
CarMapper mapper = sqlSession.getMapper(CarMapper.class);
int count = mapper.deleteById(29L);
System.out.println("删除了几条记录:" + count);
sqlSession.commit();
sqlSession.close();
}
CarMapper接口
/**
* 使用@Update()注解更新车辆信息
* @param car
* @return
*/
@Update("update t_car set car_num=#{carNum}, brand=#{brand}, guide_price=#{guidePrice}, produce_time=#{produceTime}, car_type=#{carType} where id = #{id}")
int updateById(Car car);
测试类:
@Test
public void testUpdateById(){
SqlSession sqlSession = SqlSessionUtil.openSession();
CarMapper mapper = sqlSession.getMapper(CarMapper.class);
Car car = new Car(44L, "1001", "凯美瑞", 30.5, "2000-11-10", "新能源");
int count = mapper.updateById(car);
System.out.println("更新了几条数据:" + count);
sqlSession.commit();
sqlSession.close();
}
CarMapper接口
/**
* 使用@Select()、@Results({@Result()...})注解更新车辆信息
* @param id
* @return
*/
@Select("select * from t_car where id = #{id}")
@Results({
@Result(column = "id", property = "id", id = true),
@Result(column = "car_num", property = "carNum"),
@Result(column = "brand", property = "brand"),
@Result(column = "guide_price", property = "guidePrice"),
@Result(column = "produce_time", property = "produceTime"),
@Result(column = "car_type", property = "carType"),
})
Car selectById(Long id);
测试类:
@Test
public void testSelectById(){
SqlSession sqlSession = SqlSessionUtil.openSession();
CarMapper mapper = sqlSession.getMapper(CarMapper.class);
Car car = mapper.selectById(44L);
System.out.println(car);
sqlSession.close();
}
执行结果:
该部分内容不再赘述,不会解析XML的,请观看⽼杜前面讲解的dom4j解析XML文件的视频。
模块名:parse-xml-by-dom4j(普通的Java Maven模块)
第一步:引入dom4j的依赖
<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.0modelVersion>
<groupId>org.groupgroupId>
<artifactId>parse-xml-by-dom4jartifactId>
<version>1.0-SNAPSHOTversion>
<packaging>jarpackaging>
<dependencies>
<dependency>
<groupId>org.dom4jgroupId>
<artifactId>dom4jartifactId>
<version>2.1.3version>
dependency>
<dependency>
<groupId>jaxengroupId>
<artifactId>jaxenartifactId>
<version>1.2.0version>
dependency>
<dependency>
<groupId>junitgroupId>
<artifactId>junitartifactId>
<version>4.13.2version>
<scope>testscope>
dependency>
dependencies>
<properties>
<maven.compiler.source>8maven.compiler.source>
<maven.compiler.target>8maven.compiler.target>
<project.build.sourceEncoding>UTF-8project.build.sourceEncoding>
properties>
project>
第二步:编写配置文件godbatis-config.xml
DOCTYPE configuration
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
<environments default="powernodeDB">
<environment id="powernodeDB">
<transactionManager type="MANAGED"/>
<dataSource type="UNPOOLED">
<property name="driver" value="com.mysql.cj.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/powernode"/>
<property name="username" value="root"/>
<property name="password" value="root"/>
dataSource>
environment>
<environment id="mybatisDB">
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<property name="driver" value="com.mysql.cj.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/mybatis"/>
<property name="username" value="root"/>
<property name="password" value="root"/>
dataSource>
environment>
environments>
<mappers>
<mapper resource="CarMapper.xml"/>
mappers>
configuration>
第三步:解析godbatis-config.xml
@Test
public void testParseMyBatisConfigXML() throws DocumentException {
// 创建SAXReader对象
SAXReader reader = new SAXReader();
// 获取输入流
InputStream is = ClassLoader.getSystemClassLoader().getResourceAsStream("mybatis-config.xml");
// 读取XML文件
Document document = reader.read(is);
// // 获取文档当中的根标签
// Element rootElt = document.getRootElement();
// String rootEltName = rootElt.getName();
// System.out.println("根节点的名字:" + rootEltName);
// 获取default默认的环境id
// xpath是做标签路径匹配的,能够让我们快速定位XML文件中的元素。
// 以下的xpath代表了:从根下开始找configuration标签,然后找configuration标签下的子标签environments
String xpath = "/configuration/environments";
Element environments = (Element) document.selectSingleNode(xpath); // Element是Node类的子类,方法更多,使用更便捷。
// 获取属性的值
String defaultEnvironmentId = environments.attributeValue("default");
System.out.println("默认环境的id:" + defaultEnvironmentId);
// 具体的环境environment
xpath = "/configuration/environments/environment[@id='" + defaultEnvironmentId + "']";
// System.out.println(xpath);
Element environment = (Element) document.selectSingleNode(xpath);
// 获取environment节点下的transactionManager节点(Element的element()方法用来获取孩子节点)
Element transactionManager = environment.element("transactionManager");
String transactionType = transactionManager.attributeValue("type");
System.out.println("事务管理器的类型:" + transactionType);
// 获取dataSource节点
Element dataSource = environment.element("dataSource");
String dataSourceType = dataSource.attributeValue("type");
System.out.println("数据源的类型:" + dataSourceType);
// 获取dataSource节点下的所有子节点
List<Element> propertyElts = dataSource.elements();
// 遍历
propertyElts.forEach(propertyElt -> {
String name = propertyElt.attributeValue("name");
String value = propertyElt.attributeValue("value");
System.out.println(name + "=" + value);
});
// 获取所有的mapper标签
// 不想从根下开始获取,你想从任意位置开始,获取所有的某个标签,xpath该这样写
xpath = "//mapper";
List<Node> mappers = document.selectNodes(xpath);
// 遍历
mappers.forEach(mapper -> {
Element mapperElt = (Element) mapper;
String resource = mapperElt.attributeValue("resource");
System.out.println(resource);
});
}
执行结果:
第四步:编写配置文件sqlmapper.xml
DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="car">
<insert id="insertCar">
insert into t_car values(null,#{carNum},#{brand},#{guidePrice},#{produceTime},#{carType})
insert>
<select id="selectById" resultType="com.powernode.mybatis.pojo.Car">
select
id,car_num as carNum,brand,guide_price as guidePrice,
produce_time as produceTime,
car_type as carType
from
t_car
where
id = #{id}
select>
mapper>
第五步:解析sqlmapper.xml
@Test
public void testParseSqlMapperXML() throws DocumentException {
SAXReader reader = new SAXReader();
InputStream is = ClassLoader.getSystemClassLoader().getResourceAsStream("CarMapper.xml");
Document document = reader.read(is);
// 获取namespace
String xpath = "/mapper";
Element mapper = (Element) document.selectSingleNode(xpath);
String namespace = mapper.attributeValue("namespace");
System.out.println(namespace);
// 获取mapper节点下的所有子节点
List<Element> elements = mapper.elements();
// 遍历
elements.forEach(element -> {
// 获取sqlId
String id = element.attributeValue("id");
System.out.print(id+"\t");
// 获取resultType
String resultType = element.attributeValue("resultType");
System.out.println(resultType);
// 获取标签中的sql语句(表示获取标签中的文本内容,而且去除前后空白)
String sql = element.getTextTrim();
System.out.println(sql);
// insert into t_car values(null,#{carNum},#{brand},#{guidePrice},#{produceTime},#{carType})
// insert into t_car values(null,?,?,?,?,?)
// mybatis封装了jdbc。早晚要执行带有?的sql语句
// 转换
String newSql = sql.replaceAll("#\\{[0-9A-Za-z_$]*}", "?");
System.out.println(newSql);
});
}
执行结果:
手写框架之前,如果没有思路,可以先参考一下mybatis的客户端程序,通过客户端程序来逆推需要的 类,参考代码:
@Test
public void testInsert(){
SqlSession sqlSession = null;
try {
// 1.创建SqlSessionFactoryBuilder对象
SqlSessionFactoryBuilder sqlSessionFactoryBuilder = new SqlSession
FactoryBuilder();
// 2.创建SqlSessionFactory对象
SqlSessionFactory sqlSessionFactory = sqlSessionFactoryBuilder.bui
ld(Resources.getResourceAsStream("mybatis-config.xml"));
// 3.创建SqlSession对象
sqlSession = sqlSessionFactory.openSession();
// 4.执行SQL
Car car = new Car(null, "111", "宝⻢X7", "70.3", "2010-10-11", "燃油车");
int count = sqlSession.insert("insertCar",car);
System.out.println("更新了几条记录:" + count);
// 5.提交
sqlSession.commit();
} catch (Exception e) {
// 回滚
if (sqlSession != null) {
sqlSession.rollback();
}
e.printStackTrace();
} finally {
// 6.关闭
if (sqlSession != null) {
sqlSession.close();
}
}
}
@Test
public void testSelectOne(){
SqlSession sqlSession = null;
try {
// 1.创建SqlSessionFactoryBuilder对象
SqlSessionFactoryBuilder sqlSessionFactoryBuilder = new SqlSessionFactoryBuilder();
// 2.创建SqlSessionFactory对象
SqlSessionFactory sqlSessionFactory = sqlSessionFactoryBuilder.build(Resources.getResourceAsStream("mybatis-config.xml"));
// 3.创建SqlSession对象
sqlSession = sqlSessionFactory.openSession();
// 4.执行SQL
Car car = (Car)sqlSession.selectOne("selectCarByCarNum", "111");
System.out.println(car);
// 5.提交
sqlSession.commit();
} catch (Exception e) {
// 回滚
if (sqlSession != null) {
sqlSession.rollback();
}
e.printStackTrace();
} finally {
// 6.关闭
if (sqlSession != null) {
sqlSession.close();
}
}
}
模块:godbatis(创建普通的Java Maven模块,打包方式jar),引入相关依赖
<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.0modelVersion>
<groupId>org.godgroupId>
<artifactId>godbatisartifactId>
<version>1.0-SNAPSHOTversion>
<packaging>jarpackaging>
<dependencies>
<dependency>
<groupId>org.dom4jgroupId>
<artifactId>dom4jartifactId>
<version>2.1.3version>
dependency>
<dependency>
<groupId>jaxengroupId>
<artifactId>jaxenartifactId>
<version>1.2.0version>
dependency>
<dependency>
<groupId>junitgroupId>
<artifactId>junitartifactId>
<version>4.13.2version>
<scope>testscope>
dependency>
dependencies>
<properties>
<maven.compiler.source>8maven.compiler.source>
<maven.compiler.target>8maven.compiler.target>
<project.build.sourceEncoding>UTF-8project.build.sourceEncoding>
properties>
project>
Resources.java
package org.god.core;
import java.io.InputStream;
/**
* 资源工具类
* @author ShiningSong
* @version 1.0
* @since 1.0
*/
public class Resources {
/**
* 从类路径中获取配置文件的输入流
* @param config
* @return 输入流,该输入流执行类路径中的配置文件
*/
public static InputStream getResourceAsStream(String config){
return Thread.currentThread().getContextClassLoader().getResourceAsStream(config);
}
}
提供一个无参数构造方法,再提供一个build方法,该build方法要返回SqlSessionFactory对象
package org.god.core;
import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;
import javax.sql.DataSource;
import java.io.InputStream;
import java.util.HashMap;
import java.util.Map;
/**
* SqlSessionFactory对象构建器
*
* @author ShiningSong
* @version 1.0
* @since 1.0
*/
public class SqlSessionFactoryBuilder {
/**
* 创建构建器对象
*/
public SqlSessionFactoryBuilder() {
}
/**
* 获取SqlSessionFactory对象
* 该方法的主要功能是:读取godbatis核心配置文件,并构建SqlSessionFactory对象
* @param inputStream 指向核心配置文件的输入流
* @return SqlSessionFactory对象
*/
public SqlSessionFactory build(InputStream inputStream) throws DocumentException {
// 解析配置文件,创建数据源对象
// 解析配置文件,创建事务管理器对象
// 解析配置文件,获取所有的SQL映射对象
// 将以上信息封装到SqlSessionFactory对象中
// 返回
return null;
}
}
事务管理器最好是定义一个接口,然后每一个具体的事务管理器都实现这个接口。
TransactionManager接口
package org.god.core;
import java.sql.Connection;
/**
* 事务管理器接口
* @author ShiningSong
* @version 1.0
* @since 1.0
*/
public interface TransactionManager {
/**
* 提交事务
*/
void commit();
/**
* 回滚事务
*/
void rollback();
/**
* 关闭事务
*/
void close();
/**
* 开启连接
*/
void openConnection();
/**
* 获取连接对象
* @return
*/
Connection getConnection();
}
GodJDBCTransaction.java
package org.god.core;
import javax.sql.DataSource;
import java.sql.Connection;
import java.sql.SQLException;
/**
* JDBC事务管理器
* @author ShiningSong
* @version 1.0
* @since 1.0
*/
public class GodJDBCTransaction implements TransactionManager{
/**
* 连接对象,控制事务时需要
*/
private Connection conn;
/**
* 数据源对象
*/
private DataSource dataSource;
/**
* 自动提交标志
* true表示自动提交
* false表示手动提交
*/
private boolean autoCommit;
/**
* 构造事务管理器对象
* @param dataSource
* @param autoCommit
*/
public GodJDBCTransaction(DataSource dataSource, boolean autoCommit) {
this.dataSource = dataSource;
this.autoCommit = autoCommit;
}
@Override
public void commit() {
try {
conn.commit();
} catch (SQLException e) {
throw new RuntimeException(e);
}
}
@Override
public void rollback() {
try {
conn.rollback();
} catch (SQLException e) {
throw new RuntimeException(e);
}
}
@Override
public void close() {
try {
conn.close();
} catch (SQLException e) {
throw new RuntimeException(e);
}
}
@Override
public void openConnection() {
try {
this.conn = dataSource.getConnection();
this.conn.setAutoCommit(this.autoCommit);
} catch (SQLException e) {
throw new RuntimeException(e);
}
}
@Override
public Connection getConnection() {
return conn;
}
}
package org.god.core;
import java.io.PrintWriter;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.sql.SQLFeatureNotSupportedException;
import java.util.logging.Logger;
/**
* 数据源实现类,不使用连接池
* @author ShiningSong
* @version 1.0
* @since 1.0
*/
public class GodUNPOOLEDDataSource implements javax.sql.DataSource{
private String url;
private String username;
private String password;
public GodUNPOOLEDDataSource(String driver, String url, String username, String password) {
try {
Class.forName(driver);
} catch (ClassNotFoundException e) {
throw new RuntimeException(e);
}
this.url = url;
this.username = username;
this.password = password;
}
@Override
public Connection getConnection() throws SQLException {
return DriverManager.getConnection(url, username, password);
}
@Override
public Connection getConnection(String username, String password) throws SQLException {
return null;
}
@Override
public <T> T unwrap(Class<T> iface) throws SQLException {
return null;
}
@Override
public boolean isWrapperFor(Class<?> iface) throws SQLException {
return false;
}
@Override
public PrintWriter getLogWriter() throws SQLException {
return null;
}
@Override
public void setLogWriter(PrintWriter out) throws SQLException {
}
@Override
public void setLoginTimeout(int seconds) throws SQLException {
}
@Override
public int getLoginTimeout() throws SQLException {
return 0;
}
@Override
public Logger getParentLogger() throws SQLFeatureNotSupportedException {
return null;
}
}
package org.god.core;
/**
* SQL映射实体类
* @author ShiningSong
* @version 1.0
* @since 1.0
*/
public class GodMappedStatement {
private String sqlId;
private String resultType;
private String sql;
private String parameterType;
private String sqlType;
@Override
public String toString() {
return "GodMappedStatement{" +
"sqlId='" + sqlId + '\'' +
", resultType='" + resultType + '\'' +
", sql='" + sql + '\'' +
", parameterType='" + parameterType + '\'' +
", sqlType='" + sqlType + '\'' +
'}';
}
public GodMappedStatement() {
}
public GodMappedStatement(String sqlId, String resultType, String sql, String parameterType, String sqlType) {
this.sqlId = sqlId;
this.resultType = resultType;
this.sql = sql;
this.parameterType = parameterType;
this.sqlType = sqlType;
}
public String getSqlId() {
return sqlId;
}
public void setSqlId(String sqlId) {
this.sqlId = sqlId;
}
public String getResultType() {
return resultType;
}
public void setResultType(String resultType) {
this.resultType = resultType;
}
public String getSql() {
return sql;
}
public void setSql(String sql) {
this.sql = sql;
}
public String getParameterType() {
return parameterType;
}
public void setParameterType(String parameterType) {
this.parameterType = parameterType;
}
public String getSqlType() {
return sqlType;
}
public void setSqlType(String sqlType) {
this.sqlType = sqlType;
}
}
package org.god.core;
import java.util.Map;
/**
* SqlSession工厂对象,使用SqlSessionFactory可以获取会话对象
* @author ShiningSong
* @version 1.0
* @since 1.0
*/
public class SqlSessionFactory {
private TransactionManager transactionManager;
private Map<String, GodMappedStatement> mappedStatemens;
public SqlSession openSession(){
transactionManager.openConnection();
SqlSession sqlSession = new SqlSession(transactionManager, mappedStatemens);
return sqlSession;
}
public SqlSessionFactory() {
}
public SqlSessionFactory(TransactionManager transactionManager, Map<String, GodMappedStatement> mappedStatemens) {
this.transactionManager = transactionManager;
this.mappedStatemens = mappedStatemens;
}
public TransactionManager getTransactionManager() {
return transactionManager;
}
public void setTransactionManager(TransactionManager transactionManager) {
this.transactionManager = transactionManager;
}
public Map<String, GodMappedStatement> getMappedStatemens() {
return mappedStatemens;
}
public void setMappedStatemens(Map<String, GodMappedStatement> mappedStatemens) {
this.mappedStatemens = mappedStatemens;
}
}
package org.god.core;
import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;
import javax.sql.DataSource;
import java.io.InputStream;
import java.util.HashMap;
import java.util.Map;
/**
* SqlSessionFactory对象构建器
*
* @author ShiningSong
* @version 1.0
* @since 1.0
*/
public class SqlSessionFactoryBuilder {
/**
* 创建构建器对象
*/
public SqlSessionFactoryBuilder() {
}
/**
* 获取SqlSessionFactory对象
* 该方法的主要功能是:读取godbatis核心配置文件,并构建SqlSessionFactory对象
* @param inputStream 指向核心配置文件的输入流
* @return SqlSessionFactory对象
*/
public SqlSessionFactory build(InputStream inputStream) throws DocumentException {
SAXReader saxReader = new SAXReader();
Document document = saxReader.read(inputStream);
Element environmentsElt = (Element) document.selectSingleNode("/configuration/environments");
String defaultEnv = environmentsElt.attributeValue("default");
Element environmentElt = (Element) document.selectSingleNode("/configuration/environments/environment[@id='" + defaultEnv + "']");
// 解析配置文件,创建数据源对象
Element dataSourceElt = environmentElt.element("dataSource");
DataSource dataSource = getDataSource(dataSourceElt);
// 解析配置文件,创建事务管理器对象
Element transactionManagerElt = environmentElt.element("transactionManager");
TransactionManager transactionManager = getTransactionManager(transactionManagerElt, dataSource);
// 解析配置文件,获取所有的SQL映射对象
// Element mappersElt = environmentsElt.element("mappers");
Element mappersElt = (Element) document.selectSingleNode("/configuration/mappers");
Map<String, GodMappedStatement> mappedStatements = getMappedStatements(mappersElt);
// 将以上信息封装到SqlSessionFactory对象中
SqlSessionFactory sqlSessionFactory = new SqlSessionFactory(transactionManager, mappedStatements);
// 返回
return sqlSessionFactory;
}
/**
* 通过mapperElt获取SQL映射实体类
* @param mappersElt
* @return
*/
private Map<String, GodMappedStatement> getMappedStatements(Element mappersElt){
Map<String, GodMappedStatement> mappedStatements = new HashMap<>();
mappersElt.elements().forEach(mapperElt -> {
try {
String resource = mapperElt.attributeValue("resource");
SAXReader saxReader = new SAXReader();
Document document = saxReader.read(Resources.getResourceAsStream(resource));
Element mapper = (Element) document.selectSingleNode("/mapper");
String namespace = mapper.attributeValue("namespace");
mapper.elements().forEach(sqlMapper -> {
String sqlId = sqlMapper.attributeValue("id");
String resultType = sqlMapper.attributeValue("resultType");
String sql = sqlMapper.getTextTrim();
String parameterType = sqlMapper.attributeValue("parameterType");
String sqlType = sqlMapper.getName().toLowerCase();
GodMappedStatement godMappedStatement = new GodMappedStatement(sqlId, resultType, sql, parameterType, sqlType);
mappedStatements.put(namespace + "." + sqlId, godMappedStatement);
});
} catch (DocumentException e) {
throw new RuntimeException(e);
}
});
return mappedStatements;
}
/**
* 通过transactionManagerElt和数据源信息创建事务
* @param transactionManagerElt
* @param dataSource
* @return
*/
private TransactionManager getTransactionManager(Element transactionManagerElt, DataSource dataSource) {
String type = transactionManagerElt.attributeValue("type").toUpperCase();
TransactionManager transactionManager = null;
if("JDBC".equals(type)){
// 使用JDBC事务
transactionManager = new GodJDBCTransaction(dataSource, false);
}else if ("MANAGED".equals(type)){
// 使用MANAGED事务,事务管理器是交给JEE容器的
}
return transactionManager;
}
/**
* 通过dataSourceElt获取数据源信息
* @param dataSourceElt
* @return
*/
private DataSource getDataSource(Element dataSourceElt) {
// 获取所有的数据源的属性配置
Map<String, String> dataSourceMap = new HashMap<>();
dataSourceElt.elements().forEach(propertyElt -> {
dataSourceMap.put(propertyElt.attributeValue("name"), propertyElt.attributeValue("value"));
});
String dataSourceType = dataSourceElt.attributeValue("type").toUpperCase();
DataSource dataSource = null;
if ("UNPOOLED".equals(dataSourceType)) {
dataSource = new GodUNPOOLEDDataSource(dataSourceMap.get("driver"), dataSourceMap.get("url"), dataSourceMap.get("username"),dataSourceMap.get("password"));
} else if ("POOLED".equals(dataSourceType)) {
} else if ("JNDI".equals(dataSourceType)){
}
return dataSource;
}
}
public SqlSession openSession(){
transactionManager.openConnection();
SqlSession sqlSession = new SqlSession(transactionManager, mappedStatemens);
return sqlSession;
}
package org.god.core;
import jdk.nashorn.internal.ir.CallNode;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.sql.*;
import java.util.HashMap;
import java.util.Map;
/**
* 创建数据库会话对象
*
* @author ShiningSong
* @version 1.0
* @since 1.0
*/
public class SqlSession {
private TransactionManager transactionManager;
private Map<String, GodMappedStatement> mappedStatements;
public SqlSession(TransactionManager transactionManager, Map<String, GodMappedStatement> mappedStatements) {
this.transactionManager = transactionManager;
this.mappedStatements = mappedStatements;
}
public void commit() {
try {
transactionManager.getConnection().commit();
} catch (SQLException e) {
throw new RuntimeException(e);
}
}
public void rollback() {
try {
transactionManager.getConnection().rollback();
} catch (SQLException e) {
throw new RuntimeException(e);
}
}
public void close() {
try {
transactionManager.getConnection().close();
} catch (SQLException e) {
throw new RuntimeException(e);
}
}
}
/**
* 插入数据
* @param sqlId 要执行的sqlId
* @param obj 要插入的数据
* @return
*/
public int insert(String sqlId, Object obj) {
GodMappedStatement godMappedStatement = mappedStatements.get(sqlId);
Connection connection = transactionManager.getConnection();
// 获取sql语句
String godbatisSql = godMappedStatement.getSql();
String sql = godbatisSql.replaceAll("#\\{[a-z0-9A-Z_\\$]*}", "?");
//重点一步
Map<Integer, String> map = new HashMap<>();
int index = 1;
while (godbatisSql.indexOf("#") >= 0) {
int beginIndex = godbatisSql.indexOf("#") + 2;
int endIndex = godbatisSql.indexOf("}");
map.put(index++, godbatisSql.substring(beginIndex, endIndex).trim());
godbatisSql = godbatisSql.substring(endIndex + 1);
}
final PreparedStatement ps;
try {
ps = connection.prepareStatement(sql);
// 给?赋值
map.forEach((k, v) -> {
try {
String getMethodName = "get" + v.toUpperCase().charAt(0) + v.substring(1);
Method getMethod = obj.getClass().getDeclaredMethod(getMethodName);
ps.setString(k, getMethod.invoke(obj).toString());
} catch (Exception e) {
throw new RuntimeException(e);
}
});
int count = ps.executeUpdate();
ps.close();
return count;
} catch (SQLException e) {
throw new RuntimeException(e);
}
}
/**
* 查询一个对象
* @param sqlId
* @param parameterObj
* @return
*/
public Object selectOne(String sqlId, Object parameterObj) {
GodMappedStatement godMappedStatement = mappedStatements.get(sqlId);
Connection connection = transactionManager.getConnection();
// 获取sql语句
String godbatisSql = godMappedStatement.getSql();
String sql = godbatisSql.replaceAll("#\\{[a-z0-9A-Z_\\$]*}", "?");
// 执行sql
PreparedStatement ps = null;
ResultSet rs = null;
Object obj = null;
try {
ps = connection.prepareStatement(sql);
ps.setString(1, parameterObj.toString());
rs = ps.executeQuery();
if (rs.next()) {
// 通过反射,将结果集封装对象
String resultType = godMappedStatement.getResultType();
Class<?> aClass = Class.forName(resultType);
Constructor<?> con = aClass.getDeclaredConstructor();
obj = con.newInstance();
// 给对象obj属性赋值
ResultSetMetaData rsmd = rs.getMetaData();
int columnCount = rsmd.getColumnCount();
for (int i = 1; i < columnCount+1; i++) {
String columnName = rsmd.getColumnName(i);
String setMethodName = "set" + columnName.toUpperCase().charAt(0) + columnName.substring(1);
Method setMethod = aClass.getDeclaredMethod(setMethodName, aClass.getDeclaredField(columnName).getType());
setMethod.invoke(obj, rs.getString(columnName));
}
}
} catch (Exception e) {
e.printStackTrace();
} finally {
if (rs != null) {
try {
rs.close();
} catch (SQLException e) {
throw new RuntimeException(e);
}
}
try {
ps.close();
} catch (SQLException e) {
throw new RuntimeException(e);
}
}
return obj;
}
使用GodBatis就和使用MyBatis是一样的。
第一步:准备数据库表t_user
第二步:创建模块,普通的Java Maven模块:godbatis-test
第三步:引入依赖pom.xml
<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.0modelVersion>
<groupId>com.powernodegroupId>
<artifactId>godbatis-testartifactId>
<version>1.0-SNAPSHOTversion>
<packaging>jarpackaging>
<dependencies>
<dependency>
<groupId>org.godgroupId>
<artifactId>godbatisartifactId>
<version>1.0-SNAPSHOTversion>
dependency>
<dependency>
<groupId>mysqlgroupId>
<artifactId>mysql-connector-javaartifactId>
<version>8.0.30version>
dependency>
<dependency>
<groupId>ch.qos.logbackgroupId>
<artifactId>logback-classicartifactId>
<version>1.2.11version>
dependency>
<dependency>
<groupId>junitgroupId>
<artifactId>junitartifactId>
<version>4.13.2version>
<scope>testscope>
dependency>
dependencies>
<properties>
<maven.compiler.source>8maven.compiler.source>
<maven.compiler.target>8maven.compiler.target>
<project.build.sourceEncoding>UTF-8project.build.sourceEncoding>
properties>
project>
第四步:编写pojo类 User.java
package com.powernode.godbatis.pojo;
/**
* 用户类
*/
public class User {
private String id;
private String name;
private String email;
private String address;
@Override
public String toString() {
return "User{" +
"id='" + id + '\'' +
", name='" + name + '\'' +
", email='" + email + '\'' +
", address='" + address + '\'' +
'}';
}
public User() {
}
public User(String id, String name, String email, String address) {
this.id = id;
this.name = name;
this.email = email;
this.address = address;
}
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
public String getAddress() {
return address;
}
public void setAddress(String address) {
this.address = address;
}
}
第五步:编写核心配置文件:godbatis-config.xml
DOCTYPE configuration
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
<properties resource="jdbc.properties"/>
<settings>
<setting name="mapUnderscoreToCamelCase" value="true"/>
<setting name="lazyLoadingEnabled" value="true"/>
settings>
<typeAliases>
<package name="com.powernode.mybatis.pojo"/>
typeAliases>
<environments default="dev">
<environment id="dev">
<transactionManager type="JDBC"/>
<dataSource type="UNPOOLED">
<property name="driver" value="com.mysql.cj.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/powernode"/>
<property name="username" value="root"/>
<property name="password" value="root"/>
dataSource>
environment>
environments>
<mappers>
<mapper resource="UserMapper.xml"/>
mappers>
configuration>
第六步:编写sql映射文件:UserMapper.xml
DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="User">
<insert id="insertUser">
insert into t_user values(#{id}, #{name}, #{email}, #{address})
insert>
<select id="selectById" resultType="com.powernode.godbatis.pojo.User">
select * from t_user where id = #{id}
select>
mapper>
第七步:编写测试类
package com.powernode.godbatis.test;
import com.powernode.godbatis.pojo.User;
import org.dom4j.DocumentException;
import org.god.core.Resources;
import org.god.core.SqlSession;
import org.god.core.SqlSessionFactory;
import org.god.core.SqlSessionFactoryBuilder;
import org.junit.Test;
import java.util.List;
public class GodbatisTest {
@Test
public void testSelectById() throws DocumentException {
SqlSessionFactoryBuilder sqlSessionFactoryBuilder = new SqlSessionFactoryBuilder();
SqlSessionFactory sqlSessionFactory = sqlSessionFactoryBuilder.build(Resources.getResourceAsStream("godbatis-config.xml"));
SqlSession sqlSession = sqlSessionFactory.openSession();
User user = (User) sqlSession.selectOne("User.selectById", 1);
System.out.println(user);
sqlSession.close();
}
@Test
public void testInsertUser() throws DocumentException {
SqlSessionFactoryBuilder sqlSessionFactoryBuilder = new SqlSessionFactoryBuilder();
SqlSessionFactory sqlSessionFactory = sqlSessionFactoryBuilder.build(Resources.getResourceAsStream("godbatis-config.xml"));
SqlSession sqlSession = sqlSessionFactory.openSession();
User user = new User("1", "zhangsan", "[email protected]", "北京大兴区");
int count = sqlSession.insert("User.insertUser", user);
System.out.println("插入了几条数据:" + count);
sqlSession.commit();
sqlSession.close();
}
}
执行结果: