SQL语句写死在java程序中,如果需要修改SQL语句,就要改java代码,违背OCP原则
JDBC代码繁琐且重复
比如获取值、创建对象、给对象的属性赋值
本质上就是对JDBC的封装,完成CRUD的操作
属于持久层框架
ORM
MyBatis框架特点
开发我的第一个MyBatis程序
第一步:打包方式jar
第二步:引入依赖
https://www.mvnrepository.com/ 寻找相关依赖
mybatis依赖
org.mybatis mybatis 3.5.10
mysql驱动依赖
mysql mysql-connector-java 8.0.30
第三步:编写mybatis核心配置文件:mybatis-config.xml
报错添加jdbc.properties至rescources
并在configuration下面
第四步:编写XxxxMapper.xml文件
在这个配置文件当中编写SQL语句。
这个文件名也不是固定的,放的位置也不是固定,我们这里给它起个名字,叫做:CarMapper.xml
把它暂时放到类的根路径下。
insert into t_car(id,car_num,brand,guide_price,produce_time,car_type)values(null,'1003','丰田',30.0,'2003-9-10','燃油车')
第五步:在mybatis-config.xml文件中指定XxxxMapper.xml文件的路径:
注意:resource属性会自动从类的根路径下开始查找资源。
第六步:编写MyBatis程序。(使用mybatis的类库,编写mybatis程序,连接数据库,做增删改查就行了。)
在MyBatis当中,负责执行SQL语句的那个对象叫做什么呢?
SqlSession
SqlSession是专门用来执行SQL语句的,是一个Java程序和数据库之间的一次会话。
要想获取SqlSession对象,需要先获取SqlSessionFactory对象,通过SqlSessionFactory工厂来生产SqlSession对象。
怎么获取SqlSessionFactory对象呢?
需要首先获取SqlSessionFactoryBuilder对象。
通过SqlSessionFactoryBuilder对象的build方法,来获取一个SqlSessionFactory对象。
mybatis的核心对象包括:
SqlSessionFactoryBuilder --> SqlSessionFactory --> SqlSession
package com.st.mybatis.test;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.FileInputStream;
import java.io.IOException;
import java.io.InputStream;/*** @author: TIKI* @Project: mybatis -MyBatisIntroductionTest* @Pcakage: com.st.mybatis.test.MyBatisIntroductionTest* @Date: 2022年10月28日 19:26* @Description:*/
public class MyBatisIntroductionTest {public static void main(String[] args) throws IOException {SqlSessionFactoryBuilder sqlSessionFactoryBuilder = new SqlSessionFactoryBuilder();// 输入流指向核心配置文件InputStream is = Resources.getResourceAsStream("mybatis-config.xml");// Resources.getResourceAsStream默认从类的根路径下开始查找资源SqlSessionFactory sqlSessionFactory = sqlSessionFactoryBuilder.build(is);SqlSession sqlSession = sqlSessionFactory.openSession();int count = sqlSession.insert("insertCar");// 执行sql语句System.out.println("插入了几条记录:" + count);sqlSession.commit();// 手动提交 底层调用了conn.commit()}
}
通过官方的这句话,你能想到什么呢?
XML是什么?
它一定是一个配置文件。
在mybatis-config.xml文件中,可以通过以下的配置进行mybatis的事务管理
type属性的值包括两个,不区分大小写
JDBC(jdbc)
JDBC事务管理器
MANAGED(managed)
MANAGED事务管理器:
mybatis 提供了 Transaction接口,该接口有两个实现类
JDBC事务管理器:
mybatis框架自己管理事务,自己采用原生的JDBC代码去管理事务:
conn.setAutoCommit(false);
开启事务。
…业务处理…
conn.commit();
手动 提交事务
使用JDBC事务管理器的话,底层创建的事务管理器对象:JdbcTransaction对象。
如果你编写的代码是下面的代码:
SqlSession sqlSession = sqlSessionFactory.openSession(true);// 表示没有开启事务。 因为这种方式压根不会执行:conn.setAutoCommit(false);
SqlSession sqlSession = sqlSessionFactory.openSession();// 底层调用conn.setAutoCommit(false);
MANAGED事务管理器:
mybatis不再负责事务的管理了。事务管理交给其它容器来负责。例如:spring。
对于我们当前的单纯的只有mybatis的情况下,如果配置为:MANAGED
那么事务这块是没人管的。没有人管理事务表示事务压根没有开启。
没有人管理事务就是没有事务。
JDBC中的事务:
如果你没有在JDBC代码中执行:conn.setAutoCommit(false);的话,默认的autoCommit是true。
在JDBC事务中,没有执行conn.setAutoCommit(false);那么autoCommit就是true。
如果autoCommit是true,就表示没有开启事务。只要执行任意一条DML语句就提交一次。
重点:
只要你的autoCommit是true(自动提交),就表示没有开启事务。
只有你的autoCommit是false的时候,就表示开启了事务。
package com.st.mybatis.test;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;/*** @author: TIKI* @Project: mybatis -MyBatisCompleteTest* @Pcakage: com.st.mybatis.test.MyBatisCompleteTest* @Date: 2022年10月29日 13:21* @Description: 完整版的MyBatis程序*/
public class MyBatisCompleteTest {public static void main(String[] args) {SqlSession sqlSession = null;try {SqlSessionFactoryBuilder sqlSessionFactoryBuilder = new SqlSessionFactoryBuilder();SqlSessionFactory sqlSessionFactory = sqlSessionFactoryBuilder.build(Resources.getResourceAsStream("mybatis-config.xml"));sqlSession = sqlSessionFactory.openSession();// 执行SQL语句,处理相关业务int count = sqlSession.insert("insertCar");System.out.println(count);// 执行到治理,没有发生任何异常,提交事务sqlSession.commit();} catch (Exception e) {// 回滚事务if (sqlSession != null) {sqlSession.rollback();}e.printStackTrace();}finally {// 关闭会话(释放资源)if (sqlSession != null) {sqlSession.close();}}}
}
单元测试(测试方法):用的是junit, junit是一个专门测试的框架(工具)。
单元测试中有两个重要的概念
在pom中加入相应的依赖
junit junit 4.13.2 test
maven项目中的src/test/java目录下,创建测试程序。
推荐的创建类和方法的提示:
测试类的名称 是Test + 你要测试的类名
例如你要测试HelloMaven , 创建测试类 TestHelloMaven
测试的方法名称 是:Test + 方法名称
package com.st.junit.service;/*** @author: TIKI* @Project: mybatis -MathService* @Pcakage: com.st.junit.service.MathService* @Date: 2022年10月29日 13:56* @Description:*/
public class MathService {public int sum(int a, int b){return a+b;}public int sub(int a, int b){return a-b;}
}
public class MathServiceTest {@Testpublic void testSum(){MathService mathService = new MathService();int actual = mathService.sum(1,2);int expected = 3;// 加断言进行测试Assert.assertEquals(expected,actual);}@Testpublic void testSub(){MathService mathService = new MathService();int actual = mathService.sub(1,2);int expected = -1;// 加断言进行测试Assert.assertEquals(expected,actual);}
}
mybatis常见的集成的日志组件有哪些呢?
SLF4J(沙拉风):沙拉风是一个日志标准
logback,它实现了沙拉风规范。
LOG4J
LOG4J2
STDOUT_LOGGING
…
注意:log4j log4j2 logback都是同一个作者开发的。
STDOUT_LOGGING是标准日志,mybatis已经实现了这种标准日志。mybatis框架本身已经实现了这种标准。只要开启即可。
怎么开启呢?在mybatis-config.xml文件中在configuration使用settings标签进行配置开启。
这个标签在编写的时候要注意,它应该出现在environments标签之前。
注意顺序。当然,不需要记忆这个顺序。
因为有dtd文件进行约束呢。我们只要参考dtd约束即可。
这种可以看到一些信息,比如:连接对象什么时候创建,什么时候关闭,sql语句是怎样的。
但是没有详细的日期,线程名字,等。如果你想使用更加丰富的配置,可以集成第三方的log组件。
集成logback日志框架。
logback日志框架实现了SLF4J标准。(沙拉风:日志门面。日志标准。)
第一步:引入logback的依赖。
ch.qos.logback logback-classic 1.2.11
第二步:引入logback所必须的xml配置文件。
[%thread] %-5level %logger{50} - %msg%n
最原始的insert代码
insert into t_car(id,car_num,brand,guide_price,produce_time,car_type)values(null,'1003','丰田霸道',30.0,'2000-10-11','燃油车');
JDBC的代码是怎么写的?
String sql = "insert into t_car(id,car_num,brand,guide_price,produce_time,car_type) values(null,?,?,?,?,?)";
ps.setString(1, xxx);
ps.setString(2, yyy);
....
在JDBC当中占位符采用的是?,在mybatis当中是什么呢?
java程序中使用Map可以给SQL语句的占位符传值:
Map map = new HashMap<>();
map.put("k1", "1111");
map.put("k2", "比亚迪汉");
map.put("k3", 10.0);
map.put("k4", "2020-11-11");
map.put("k5", "电车");
MyBatis的insert代码
insert into t_car(id,car_num,brand,guide_price,produce_time,car_type) values(null,#{carNum},#{brand},#{guidePrice},#{produceTime},#{carType});
注意:#{}这里写什么?写map集合的key,如果key不存在,获取的是null
一般map集合的key起名的时候要见名知意。
测试代码
public class CarMapperTest {@Testpublic void testInsertCar(){SqlSession sqlSession = SqlSessionUtil.openSession();// 前段传过来的数据Map map = new HashMap<>();map.put("carNum", "1111");map.put("brand", "比亚迪汉2");map.put("guidePrice", 10.0);map.put("produceTime", "2020-11-11");map.put("carType", "电车");sqlSession.insert("insertCar",map);sqlSession.commit();sqlSession.close();}
}
java程序中使用POJO类给SQL语句的占位符传值:
Car car = new Car(null, “3333”, “比亚迪秦”, 30.0, “2020-11-11”, “新能源”);
注意:占位符#{},大括号里面写:pojo类的属性名
insert into t_car(id,car_num,brand,guide_price,produce_time,car_type)
values(null,#{carNum},#{brand},#{guidePrice},#{produceTime},#{carType})
如果把SQL语句写成这个德行:
insert into t_car(id,car_num,brand,guide_price,produce_time,car_type)values(null,#{xyz},#{brand},#{guidePrice},#{produceTime},#{carType})
出现了什么问题呢?
There is no getter for property named ‘xyz’ in ‘class com.powernode.mybatis.pojo.Car’
mybatis去找:Car类中的getXyz()方法去了。没找到。报错了。
怎么解决的?
可以在Car类中提供一个getXyz()方法。这样问题就解决了。
通过这个测试,得出一个结论:如果使用POJO对象传递值的话,#{}这个大括号中到底写什么?
写的是get方法的方法名去掉get,然后将剩下的单词首字母小写,然后放进去。
也就是说mybatis在底层给?传值的时候,先要获取值,怎么获取的?
调用了pojo对象的get方法。例如:car.getCarNum(),car.getCarType(),car.getBrand()
测试代码
@Testpublic void testInsertCarByPojo(){SqlSession sqlSession = SqlSessionUtil.openSession();// 封装数据Car car= new Car(null,"333","比亚迪秦",30.0,"2020-10-20","新能源");sqlSession.insert("insertCar",car);sqlSession.commit();sqlSession.close();}
实现:
int count = sqlSession.delete("deleteById", 59);
delete from t_car where id = #{fdsfd}
注意:如果占位符只有一个,那么#{}的大括号里可以随意。但是最好见名知意。
实现:
update t_car setcar_num=#{carNum},brand=#{brand},guide_price=#{guidePrice},produce_time=#{produceTime},car_type=#{carType}whereid = #{id}
Car car = new Car(4L, "9999", "凯美瑞", 30.3, "1999-11-10", "燃油车");
int count = sqlSession.update("updateById", car);
根据主键查询的话,返回的结果一定是一个。
实现
Object car = sqlSession.selectOne("selectById", 1);
需要特别注意的是:
输出结果有点不对劲:
Car{id=1, carNum=‘null’, brand=‘宝马520’, guidePrice=null, produceTime=‘null’, carType=‘null’}
carNum以及其他的这几个属性没有赋上值的原因是什么?
select * from t_car where id = 1执行结果:+----+---------+-----------+-------------+--------------+----------+| id | car_num | brand | guide_price | produce_time | car_type |+----+---------+-----------+-------------+--------------+----------+| 1 | 1001 | 宝马520Li | 10.00 | 2020-10-11 | 燃油车 |+----+---------+-----------+-------------+--------------+----------+
car_num、guide_price、produce_time、car_type这是查询结果的列名。
这些列名和Car类中的属性名对不上。
Car类的属性名:carNum、guidePrice、produceTime、carType
那这个问题怎么解决呢?
select语句查询的时候,查询结果集的列名使用as关键字起别名的。
起别名之后:
+----+--------+-----------+------------+-------------+---------+
| id | carNum | brand | guidePrice | produceTime | carType |
+----+--------+-----------+------------+-------------+---------+
| 1 | 1001 | 宝马520Li | 10.00 | 2020-10-11 | 燃油车 |
+----+--------+-----------+------------+-------------+---------+
实现
List
注意:resultType还是指定要封装的结果集的类型。不是指定List类型,是指定List集合中元素的类型。
selectList方法:mybatis通过这个方法就可以得知你需要一个List集合。它会自动给你返回一个List集合。
在sql mapper.xml文件当中有一个namespace,这个属性是用来指定命名空间的。用来防止id重复。
怎么用?
在xml文件中:
在java程序中的写法:
List
实际上,本质上,mybatis中的sqlId的完整写法: namespace.id
第一行表明xml文件根标签的内容,一个xml只有一个根,以及采用的dtd约束
java.util.Properties类。是一个Map集合。key和value都是String类型
在properties标签中可以配置很多属性
使用
使用属性配置文件
jdbc.driver=com.mysql.cj.jdbc.Driver
jdbc.jdbc.jdbc.url=jdbc:mysql://localhost:3306/st
jdbc.jdbc.username=root
jdbc.password=123
一个configuration中可以包含多个环境
一个环境对应一个数据库
一个环境对应一个SqlSessionFactory对象
如何配置环境?
default
默认使用的环境 ID(比如:default=“development”)。
id
每个 environment 元素定义的环境 ID(比如:id=“development”)。
transactionManager
事务管理器的配置(比如:type=“JDBC”)。
dataSource
数据源的配置,使用哪个数据库连接池(比如:type=“POOLED”)
根据环境id创建SqlSessionFactory对象
SqlSessionFactoryBuilder sqlSessionFactoryBuilder = new SqlSessionFactoryBuilder();
SqlSessionFactory sqlSessionFactory = sqlSessionFactoryBuilder.build(Resources.getResourceAsStream("mybatis-config.xml"),"tiki");
SqlSession sqlSession = sqlSessionFactory.openSession();
// sql语句
sqlSession.commit();
sqlSession.close();
为程序提供Connection对象 [但凡是给程序提供Connection对象的,都叫做数据源]
数据源实际上是一套规范,JDK中有这套规范(接口):javax.sql.DataSource
数据库连接池实现了该接口,所以就是数据源
常见的数据源组件【数据库连接池】
type属性用来指定数据源的类型,就是指定具体使用什么方式来获取Connection对象
type=“[UNPOOLED|POOLED|JNDI]”
连接池的优点
UNPOOLED参数配置
driver
– 这是 JDBC 驱动的 Java 类全限定名(并不是 JDBC 驱动中可能包含的数据源类)。url
– 这是数据库的 JDBC URL 地址。username
– 登录数据库的用户名。password
– 登录数据库的密码。defaultTransactionIsolationLevel
– 默认的连接事务隔离级别。defaultNetworkTimeout
– 等待数据库操作完成的默认网络超时时间(单位:毫秒)。查看 java.sql.Connection#setNetworkTimeout()
的 API 文档以获取更多信息除了以上参数外,POOLED池中常见参数配置有:
poolMaximumActiveConnections:连接池当中最多的正在使用的连接对象的数量上限。
最多有多少个连接可以活动。默认值10
poolTimeToWait:如果获取连接花费了相当长的时间,连接池会每隔2秒打印日志,并且尝试获取连接对象
poolMaximumCheckoutTime:在被强制返回之前,池中连接被检出(checked out)时间
默认值:20000 毫秒(即 20 秒)
poolMaximumIdleConnections:最多的空闲数量
指定SQL映射文件的路径
4.0.0 com.st parse-xml-by-dom4j 1.0-SNAPSHOT jar org.dom4j dom4j 2.1.3 jaxen jaxen 1.2.0 junit junit 4.13.2 test 17 17
@Testpublic void testParseMyBatisConfigXML() throws Exception{// 创建SAXReader对象SAXReader reader = new SAXReader();// 获取输入流InputStream is = ClassLoader.getSystemClassLoader().getResourceAsStream("mybatis-config.xml");// 读XML文件,返回document对象。document对象是文档对象,代表了整个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标签下的子标签environmentsString xpath = "/configuration/environments";Element environments = (Element) document.selectSingleNode(xpath); // Element是Node类的子类,方法更多,使用更便捷。// 获取属性的值String defaultEnvironmentId = environments.attributeValue("default");//System.out.println("默认环境的id:" + defaultEnvironmentId);// 获取具体的环境environmentxpath = "/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 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 mappers = document.selectNodes(xpath);// 遍历mappers.forEach(mapper -> {Element mapperElt = (Element) mapper;String resource = mapperElt.attributeValue("resource");System.out.println(resource);});}
insert into t_car values(null,#{carNum},#{brand},#{guidePrice},#{produceTime},#{carType})
@Testpublic void testParseSqlMapperXML() throws Exception{SAXReader reader = new SAXReader();InputStream is = ClassLoader.getSystemClassLoader().getResourceAsStream("CarMapper.xml");Document document = reader.read(is);// 获取namespaceString xpath = "/mapper";Element mapper = (Element) document.selectSingleNode(xpath);String namespace = mapper.attributeValue("namespace");System.out.println(namespace);// 获取mapper节点下所有的子节点List elements = mapper.elements();// 遍历elements.forEach(element -> {// 获取sqlIdString id = element.attributeValue("id");System.out.println(id);// 获取resultTypeString resultType = element.attributeValue("resultType"); // 没有这个属性的话,会自动返回"null"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,?,?,?,?,?)// mybaits封装了jdbc。早晚要执行带有?的sql语句。// 转换String newSql = sql.replaceAll("#\\{[0-9A-Za-z_$]*}", "?");System.out.println(newSql);});}
手写框架之前,如果没有思路,可以先参考一下mybatis的客户端程序,通过客户端程序来逆推需要的
类,参考代码:
package com.st.mybatis.test;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;/*** @author: TIKI* @Project: mybatis -MyBatisCompleteTest* @Pcakage: com.st.mybatis.test.MyBatisCompleteTest* @Date: 2022年10月29日 13:21* @Description: 完整版的MyBatis程序*/
public class MyBatisCompleteTest {public static void main(String[] args) {SqlSession sqlSession = null;try {SqlSessionFactoryBuilder sqlSessionFactoryBuilder = new SqlSessionFactoryBuilder();SqlSessionFactory sqlSessionFactory = sqlSessionFactoryBuilder.build(Resources.getResourceAsStream("mybatis-config.xml"));sqlSession = sqlSessionFactory.openSession();// 执行SQL语句,处理相关业务int count = sqlSession.insert("insertCar");System.out.println(count);// 执行到治理,没有发生任何异常,提交事务sqlSession.commit();} catch (Exception e) {// 回滚事务if (sqlSession != null) {sqlSession.rollback();}e.printStackTrace();}finally {// 关闭会话(释放资源)if (sqlSession != null) {sqlSession.close();}}}
}
模块:godbatis(创建普通的Java Maven模块,打包方式jar),引入相关依赖
4.0.0 org.god.ibatis godbatis 1.0 jar org.dom4j dom4j 2.1.3 jaxen jaxen 1.2.0 junit junit 4.13.2 test mysql mysql-connector-java 8.0.30 17 17
这个工具类专门完成“类路径”中资源的加载。
工具类的构造方法都是建议私有化的。
代码
package org.god.ibatis.utils;import java.io.InputStream;/*** godbatis框架提供的一个工具类。* 这个工具类专门完成“类路径”中资源的加载。* @author 动力节点* @since 1.0* @version 1.0*/
public class Resources {/*** 工具类的构造方法都是建议私有化的。* 因为工具类中的方法都是静态的,不需要创建对象就能调用。* 为了避免new对象,所有构造方法私有化。* 这只是一种编程习惯。*/private Resources(){}/*** 从类路径当中加载资源。* @param resource 放在类路径当中的资源文件。* @return 指向资源文件的一个输入流。*/public static InputStream getResourceAsStream(String resource){return ClassLoader.getSystemClassLoader().getResourceAsStream(resource);}
}
SqISessionFactoryBuilder:SqlSessionFactory构建器对象。
通过SqlSessionFactoryBuilder的build
方法来解析godbatis-config.xml文件,然后创建SqlSessionFactory对象。
SqISessionFactoryBuilder.build方法返回一个SqlSessionFactory类对象,那么这个对象应该具有哪些属性呢?
根据核心配置文件,SqlSessionFactory类中至少有以下属性
事务管理器属性:可以灵活切换->接口
数据源属性:分析可得SqlSessionFactory类中可以不设置数据源
事务管理器对象中需要数据源对象获取连接对象Connection,因此可以通过事务管理器对象获取数据源对象
一个大的Map集合:存储所有mapper映射文件的sql语句,key为sqlId
,value为封装好的SQL标签信息对象MappedStatement
Transaction事务管理器接口:提供管理事务方法
代码
package org.god.ibatis.core;import java.sql.Connection;/*** 事务管理器接口。* 所有的事务管理器都应该遵循该规范。* JDBC事务管理器,MANAGED事务管理器都应该实现这个接口。* Transaction事务管理器:提供管理事务方法。* @author 动力节点* @version 1.0* @since 1.0*/
public interface Transaction {/*** 提交事务*/void commit();/*** 回滚事务*/void rollback();/*** 关闭事务*/void close();/*** 真正的开启数据库连接。*/void openConnection();/*** 获取数据库连接对象的。*/Connection getConnection();
}
根据sql标签,定义SQL标签信息对象MappedStatement[简单的]
属性
private String sql;
sql语句
private String resultType;
要封装的结果集类型。有的时候resultType是null。
package org.god.ibatis.core;/*** 普通的java类。POJO,封装了一个SQL标签。* 一个MappedStatement对象对应一个SQL标签。* 一个SQL标签中的所有信息封装到MappedStatement对象当中。* 面向对象编程思想。* @author 动力节点* @version 1.0* @since 1.0*/
public class MappedStatement {/*** sql语句*/private String sql;/*** 要封装的结果集类型。有的时候resultType是null。* 比如:insert delete update语句的时候resultType是null。* 只有当sql语句是select语句的时候resultType才有值。*/private String resultType;@Overridepublic String toString() {return "MappedStatement{" +"sql='" + sql + '\'' +", resultType='" + resultType + '\'' +'}';}public String getSql() {return sql;}public void setSql(String sql) {this.sql = sql;}public String getResultType() {return resultType;}public void setResultType(String resultType) {this.resultType = resultType;}public MappedStatement(String sql, String resultType) {this.sql = sql;this.resultType = resultType;}public MappedStatement() {}
}
GodBatis只对JdbcTransaction进行实现
思路
控制事务的时候需要通过连接对象Connecton进行事务的提交、回滚以及关闭
那么Connection对象从哪里来?—>通过属性数据源获得Connection对象
因此SqlSessionFactory类中可以不设置数据源属性:通过事务管理器对象获取数据源对象
commit、rollback、close方法中需使用一个Connection对象,因此需要添加属性connection,通过openConnection方法对空的connection进行赋值,真正开启数据库连接
属性
方法
通过数据源对事务管理器进行完善代码
package org.god.ibatis.core;import javax.sql.DataSource;
import java.sql.Connection;
import java.sql.SQLException;/*** JDBC事务管理器(godbatis框架目前只对JdbcTransaction进行实现。)* @author 动力节点* @version 1.0* @since 1.0*/
public class JdbcTransaction implements Transaction{/*** 数据源属性* 经典的设计:面向接口编程。*/private DataSource dataSource;/*** 自动提交标志* true表示自动提交* false表示不采用自动提交*/private boolean autoCommit;/*** 连接对象*/private Connection connection;@Overridepublic Connection getConnection() {return connection;}/*** 创建事务管理器对象* @param dataSource* @param autoCommit*/public JdbcTransaction(DataSource dataSource, boolean autoCommit) {this.dataSource = dataSource;this.autoCommit = autoCommit;}@Overridepublic void commit() {try {connection.commit();} catch (SQLException e) {e.printStackTrace();}}@Overridepublic void rollback() {try {connection.rollback();} catch (SQLException e) {e.printStackTrace();}}@Overridepublic void close() {try {connection.close();} catch (SQLException e) {e.printStackTrace();}}@Overridepublic void openConnection(){if (connection == null) {try {connection = dataSource.getConnection();// 开启事务connection.setAutoCommit(autoCommit);} catch (SQLException e) {e.printStackTrace();}}}
}
数据源用于获取connection对象
数据源种类有:POOLED UNPOOLED JNDI
UnPooledDataSource实现类
package org.god.ibatis.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;/*** 数据源的实现类:UNPOOLED (重点实现这种方式。)* 不使用连接池,每一次都新建Connection对象。* @author 动力节点* @version 1.0* @since 1.0*/
public class UnPooledDataSource implements javax.sql.DataSource{private String url;private String username;private String password;/*** 创建一个数据源对象。* @param driver* @param url* @param username* @param password*/public UnPooledDataSource(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;}@Overridepublic Connection getConnection() throws SQLException {Connection connection = DriverManager.getConnection(url, username, password);return connection;}@Overridepublic Connection getConnection(String username, String password) throws SQLException {return null;}@Overridepublic PrintWriter getLogWriter() throws SQLException {return null;}@Overridepublic void setLogWriter(PrintWriter out) throws SQLException {}@Overridepublic void setLoginTimeout(int seconds) throws SQLException {}@Overridepublic int getLoginTimeout() throws SQLException {return 0;}@Overridepublic Logger getParentLogger() throws SQLFeatureNotSupportedException {return null;}@Overridepublic T unwrap(Class iface) throws SQLException {return null;}@Overridepublic boolean isWrapperFor(Class> iface) throws SQLException {return false;}
}
/*** 获取事务管理器* @param transactionElt 事务管理器标签元素* @param dataSource 数据源对象* @return*/private Transaction getTransaction(Element transactionElt, DataSource dataSource) {Transaction transaction = null;String type = transactionElt.attributeValue("type").trim().toUpperCase();if (Const.JDBC_TRANSACTION.equals(type)) {transaction = new JdbcTransaction(dataSource, false); // 默认是开启事务的,将来需要手动提交的。}if (Const.MANAGED_TRANSACTION.equals(type)) {transaction = new ManagedTransaction();}return transaction;}/*** 获取数据源对象* @param dataSourceElt 数据源标签元素* @return*/private DataSource getDataSource(Element dataSourceElt) {Map map = new HashMap<>();// 获取所有的propertyList propertyElts = dataSourceElt.elements("property");propertyElts.forEach(propertyElt -> {String name = propertyElt.attributeValue("name");String value = propertyElt.attributeValue("value");map.put(name, value);});DataSource dataSource = null;String type = dataSourceElt.attributeValue("type").trim().toUpperCase();// 去空格并转化为大写if (Const.UN_POOLED_DATASOURCE.equals(type)) {dataSource = new UnPooledDataSource(map.get("driver"), map.get("url"), map.get("username"), map.get("password"));}if (Const.POOLED_DATASOURCE.equals(type)) {dataSource = new PooledDataSource();}if (Const.JNDI_DATASOURCE.equals(type)) {dataSource = new JNDIDataSource();}return dataSource;}}
/*** 解析所有的SqlMapper.xml文件,然后构建Map集合。* @param sqlMapperXMLPathList* @return*/private Map getMappedStatements(List sqlMapperXMLPathList) {Map mappedStatements = new HashMap<>();sqlMapperXMLPathList.forEach(sqlMapperXMLPath -> {try {SAXReader reader = new SAXReader();Document document = reader.read(Resources.getResourceAsStream(sqlMapperXMLPath));Element mapper = (Element) document.selectSingleNode("mapper");// 根mapperString namespace = mapper.attributeValue("namespace");// 防止id重复List elements = mapper.elements();elements.forEach(element -> {String id = element.attributeValue("id");// 这里进行了namespace和id的拼接,生成最终的sqlIdString sqlId = namespace + "." + id;String resultType = element.attributeValue("resultType");String sql = element.getTextTrim();//除去前后空白MappedStatement mappedStatement = new MappedStatement(sql, resultType);mappedStatements.put(sqlId, mappedStatement);});} catch (Exception e) {e.printStackTrace();}});return mappedStatements;}
技巧:将常量定义在Const类中
主要方法
public SqlSessionFactory build(InputStream in);
解析godbatis-config.xml文件,来构建SqlSessionFactory对象。
private Map
解析所有的SqlMapper.xml文件,然后构建Map集合。
private Transaction getTransaction(Element transactionElt, DataSource dataSource)
获取事务管理器
private DataSource getDataSource(Element dataSourceElt)
获取数据源对象
代码
package org.god.ibatis.core;import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.Element;
import org.dom4j.Node;
import org.dom4j.io.SAXReader;
import org.god.ibatis.utils.Resources;import javax.sql.DataSource;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;/*** SqlSessionFactory构建器对象。* 通过SqlSessionFactoryBuilder的build方法来解析* godbatis-config.xml文件,然后创建SqlSessionFactory对象。* @author 动力节点* @version 1.0* @since 1.0*/
public class SqlSessionFactoryBuilder {/*** 无参数构造方法。*/public SqlSessionFactoryBuilder(){}/*** 解析godbatis-config.xml文件,来构建SqlSessionFactory对象。* @param in 指向godbatis-config.xml文件的一个输入流。* @return SqlSessionFactory对象。*/public SqlSessionFactory build(InputStream in){SqlSessionFactory factory = null;try {// 解析godbatis-config.xml文件SAXReader reader = new SAXReader();Document document = reader.read(in);Element environments = (Element) document.selectSingleNode("/configuration/environments");String defaultId = environments.attributeValue("default");Element environment = (Element) document.selectSingleNode("/configuration/environments/environment[@id='" + defaultId + "']");Element transactionElt = environment.element("transactionManager");Element dataSourceElt = environment.element("dataSource");List sqlMapperXMLPathList = new ArrayList<>();List nodes = document.selectNodes("//mapper"); // //获取整个配置文件中所有的mapper标签nodes.forEach(node -> {Element mapper = (Element) node;String resource = mapper.attributeValue("resource");sqlMapperXMLPathList.add(resource);});// 获取数据源对象DataSource dataSource = getDataSource(dataSourceElt);// 获取事务管理器Transaction transaction = getTransaction(transactionElt,dataSource);// 获取mappedStatementsMap mappedStatements = getMappedStatements(sqlMapperXMLPathList);// 解析完成之后,构建SqlSessionFactory对象。factory = new SqlSessionFactory(transaction, mappedStatements);} catch (Exception e) {e.printStackTrace();}return factory;}/*** 解析所有的SqlMapper.xml文件,然后构建Map集合。* @param sqlMapperXMLPathList* @return*/private Map getMappedStatements(List sqlMapperXMLPathList) {Map mappedStatements = new HashMap<>();sqlMapperXMLPathList.forEach(sqlMapperXMLPath -> {try {SAXReader reader = new SAXReader();Document document = reader.read(Resources.getResourceAsStream(sqlMapperXMLPath));Element mapper = (Element) document.selectSingleNode("mapper");// 根mapperString namespace = mapper.attributeValue("namespace");// 防止id重复List elements = mapper.elements();elements.forEach(element -> {String id = element.attributeValue("id");// 这里进行了namespace和id的拼接,生成最终的sqlIdString sqlId = namespace + "." + id;String resultType = element.attributeValue("resultType");String sql = element.getTextTrim();//除去前后空白MappedStatement mappedStatement = new MappedStatement(sql, resultType);mappedStatements.put(sqlId, mappedStatement);});} catch (Exception e) {e.printStackTrace();}});return mappedStatements;}/*** 获取事务管理器* @param transactionElt 事务管理器标签元素* @param dataSource 数据源对象* @return*/private Transaction getTransaction(Element transactionElt, DataSource dataSource) {Transaction transaction = null;String type = transactionElt.attributeValue("type").trim().toUpperCase();if (Const.JDBC_TRANSACTION.equals(type)) {transaction = new JdbcTransaction(dataSource, false); // 默认是开启事务的,将来需要手动提交的。}if (Const.MANAGED_TRANSACTION.equals(type)) {transaction = new ManagedTransaction();}return transaction;}/*** 获取数据源对象* @param dataSourceElt 数据源标签元素* @return*/private DataSource getDataSource(Element dataSourceElt) {Map map = new HashMap<>();// 获取所有的propertyList propertyElts = dataSourceElt.elements("property");propertyElts.forEach(propertyElt -> {String name = propertyElt.attributeValue("name");String value = propertyElt.attributeValue("value");map.put(name, value);});DataSource dataSource = null;String type = dataSourceElt.attributeValue("type").trim().toUpperCase();// 去空格并转化为大写if (Const.UN_POOLED_DATASOURCE.equals(type)) {dataSource = new UnPooledDataSource(map.get("driver"), map.get("url"), map.get("username"), map.get("password"));}if (Const.POOLED_DATASOURCE.equals(type)) {dataSource = new PooledDataSource();}if (Const.JNDI_DATASOURCE.equals(type)) {dataSource = new JNDIDataSource();}return dataSource;}}
openSession方法:获取Sql会话对象
/*** 获取Sql会话对象。* @return*/public SqlSession openSession(){// 开启会话的前提是开启连接。(连接打开了)transaction.openConnection();// 创建SqlSession对象SqlSession sqlSession = new SqlSession(this);// 将SqlSessionFactory传入return sqlSession;}
SqlSession私有变量 构造方法
private SqlSessionFactory factory;public SqlSession(SqlSessionFactory factory) {this.factory = factory;}
代码
/*** 提交事务*/public void commit(){factory.getTransaction().commit();}/*** 回滚事务*/public void rollback(){factory.getTransaction().rollback();}/*** 关闭事务*/public void close(){factory.getTransaction().close();}
思路:将原sql转换为jdbc中的sql,并动态给占位符赋值
代码
/*** 执行insert语句,向数据库表当中插入记录。* @param sqlId sql语句的id* @param pojo 插入的数据。* @return*/public int insert(String sqlId, Object pojo){int count = 0;try {// JDBC代码,执行insert语句,完成插入操作。Connection connection = factory.getTransaction().getConnection();// insert into t_user values(#{id},#{name},#{age})String godbatisSql = factory.getMappedStatements().get(sqlId).getSql();// insert into t_user(id,name,age) values(?,?,?)String sql = godbatisSql.replaceAll("#\\{[a-zA-Z0-9_$]*}", "?");PreparedStatement ps = connection.prepareStatement(sql);// 给?占位符传值// 难度是什么:// 第一:你不知道有多少个?// 第二:你不知道该将pojo对象中的哪个属性赋值给哪个 ?// ps.String(第几个问号, 传什么值); // 这里都是setString,所以数据库表中的字段类型要求都是varchar才行。这是godbatis比较失败的地方。int fromIndex = 0;int index = 1;while(true){int jingIndex = godbatisSql.indexOf("#", fromIndex);if (jingIndex < 0) {break;}int youKuoHaoIndex = godbatisSql.indexOf("}", fromIndex);String propertyName = godbatisSql.substring(jingIndex + 2, youKuoHaoIndex).trim();fromIndex = youKuoHaoIndex + 1;// 有属性名id,怎么获取id的属性值呢?调用getId()方法String getMethodName = "get" + propertyName.toUpperCase().charAt(0) + propertyName.substring(1);Method getMethod = pojo.getClass().getDeclaredMethod(getMethodName);Object propertyValue = getMethod.invoke(pojo);ps.setString(index, propertyValue.toString());index++;}// 执行SQL语句count = ps.executeUpdate();} catch (Exception e) {e.printStackTrace();}return count;}
思路
代码
/*** 执行查询语句,返回一个对象。该方法只适合返回一条记录的sql语句。* @param sqlId* @param param* @return*/public Object selectOne(String sqlId, Object param){Object obj = null;try {Connection connection = factory.getTransaction().getConnection();MappedStatement mappedStatement = factory.getMappedStatements().get(sqlId);// 这是那个DQL查询语句// select * from t_user where id = #{id}String godbatisSql = mappedStatement.getSql();String sql = godbatisSql.replaceAll("#\\{[a-zA-Z0-9_$]*}", "?");PreparedStatement ps = connection.prepareStatement(sql);// 给占位符传值ps.setString(1, param.toString());// 查询返回结果集ResultSet rs = ps.executeQuery();// 要封装的结果类型。String resultType = mappedStatement.getResultType(); // org.god.ibatis.pojo.User// 从结果集中取数据,封装java对象if (rs.next()) {// 获取resultType的ClassClass> resultTypeClass = Class.forName(resultType);// 调用无参数构造方法创建对象obj = resultTypeClass.newInstance(); // Object obj = new User();// 给User类的id,name,age属性赋值// 给obj对象的哪个属性赋哪个值。/*mysql> select * from t_user where id = '1111';+------+----------+------+| id | name | age |+------+----------+------+| 1111 | zhangsan | 20 |+------+----------+------+解决问题的关键:将查询结果的列名作为属性名。列名是id,那么属性名就是:id列名是name,那么属性名就是:name*/ResultSetMetaData rsmd = rs.getMetaData();int columnCount = rsmd.getColumnCount();for (int i = 0; i < columnCount; i++) {String propertyName = rsmd.getColumnName(i + 1);// 拼接方法名String setMethodName = "set" + propertyName.toUpperCase().charAt(0) + propertyName.substring(1);// 获取set方法Method setMethod = resultTypeClass.getDeclaredMethod(setMethodName, String.class);// 调用set方法给对象obj属性赋值setMethod.invoke(obj, rs.getString(propertyName));}}} catch (Exception e) {e.printStackTrace();}return obj;}// 局部测试public static void main(String[] args) {String sql = "insert into t_user values(#{id},#{name},#{age})";int fromIndex = 0;int index = 1;while(true){int jingIndex = sql.indexOf("#", fromIndex);if (jingIndex < 0) {break;}System.out.println(index);index++;int youKuoHaoIndex = sql.indexOf("}", fromIndex);String propertyName = sql.substring(jingIndex + 2, youKuoHaoIndex).trim();System.out.println(propertyName);fromIndex = youKuoHaoIndex + 1;}}
完成银行账户转账的功能
创建maven web项目
默认创建的maven web应用没有java和sesources目录
配置tomcat
修改web.xml文件为高版本
确定pom.xml文件中的打包方式是war包
pom.xml 引入相关依赖
4.0.0 com.st mybatis004-web 1.0-SNAPSHOT war mybatis-004-web Maven Webapp http://www.example.com UTF-8 1.7 1.7 org.mybatis mybatis 3.5.10 mysql mysql-connector-java 8.0.30 ch.qos.logback logback-classic 1.2.11 javax.servlet javax.servlet-api 4.0.1 mybatis004-web maven-clean-plugin 3.1.0 maven-resources-plugin 3.0.2 maven-compiler-plugin 3.8.0 maven-surefire-plugin 2.22.1 maven-war-plugin 3.2.2 maven-install-plugin 2.5.2 maven-deploy-plugin 2.8.2
引入相关配置文件,放入resources目录
mybatis-config.xml
AccountMapper.xml
logback.xml
[%thread] %-5level %logger{50} - %msg%n
jdbc.properties
银行账户转账
Account
package com.st.bank.pojo;/*** @author: TIKI* @Project: mybatis -Account* @Pcakage: com.st.bank.pojo.Account* @Date: 2022年11月12日 20:18* @Description:银行账户类*/
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;}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;}@Overridepublic String toString() {return "Account{" +"id=" + id +", actno='" + actno + '\'' +", balance=" + balance +'}';}
}
分析dao中至少需要提供几个方法,才能完成转账
AccountDao接口
package com.st.bank.dao;import com.st.bank.pojo.Account;/*** 账户的DAO对象,负责t_act表中数据的CRUD*/
public interface AccountDao {int updateAccount(Account account);Account selectByActno(String actno);
}
AccountDanImp实现类
package com.st.bank.dao.impl;import com.st.bank.dao.AccountDao;
import com.st.bank.pojo.Account;
import com.st.bank.utils.SqlSessionUtil;
import org.apache.ibatis.session.SqlSession;/*** @author: TIKI* @Project: mybatis -AccountDaoImpl* @Pcakage: com.st.bank.dao.impl.AccountDaoImpl* @Date: 2022年11月15日 16:12* @Description:*/
public class AccountDaoImpl implements AccountDao {@Overridepublic int updateAccount(Account account) {SqlSession sqlSession = SqlSessionUtil.openSession();int count = sqlSession.update("account.updateAccount",account);sqlSession.commit();sqlSession.close();return count;}@Overridepublic Account selectByActno(String actno) {SqlSession sqlSession = SqlSessionUtil.openSession();Account account = (Account) sqlSession.selectOne("account.selectByActno", actno);sqlSession.close();return account;}
}
update t_act set balance = #{balance} where actno = #{actno};
package com.st.bank.service;import com.st.bank.exceptions.MoneyNotEnoughException;
import com.st.bank.exceptions.TransferException;/*** 账户业务类*/
public interface AccountService {/** 账户转账业务* @param fromActno 转出账户* @param toActno 转入账户* @param money 转账金额*/void transfer(String fromActno, String toActno, double money) throws MoneyNotEnoughException, TransferException;
}
AccountServiceImpl实现类
package com.st.bank.service.impl;import com.st.bank.dao.AccountDao;
import com.st.bank.dao.impl.AccountDaoImpl;
import com.st.bank.exceptions.MoneyNotEnoughException;
import com.st.bank.exceptions.TransferException;
import com.st.bank.pojo.Account;
import com.st.bank.service.AccountService;/*** @author: TIKI* @Project: mybatis -AccountServiceImpl* @Pcakage: com.st.bank.service.impl.AccountServiceImpl* @Date: 2022年11月15日 16:03* @Description:*/
public class AccountServiceImpl implements AccountService {private AccountDao accountDao = new AccountDaoImpl();@Overridepublic void transfer(String fromActno, String toActno, double money) throws MoneyNotEnoughException, TransferException {// 1. 判断转出账户的余额是否充足(select)Account fromAccount = accountDao.selectByActno(fromActno);if (fromAccount.getBalance() < money){// 2. 如果转出账户余额不足,提示用户exceptionthrow new MoneyNotEnoughException("对不起,余额不足");}// 3. 如果转出账户余额充足,更新转出账户余额(update)fromAccount.setBalance(fromAccount.getBalance() - money);int count = accountDao.updateAccount(fromAccount);// 4. 更新转入账户余额(update)Account toAccount = accountDao.selectByActno(toActno);toAccount.setBalance(toAccount.getBalance() + money);count += accountDao.updateAccount(toAccount);if (count != 2) {throw new TransferException("转账失败");}}
}
MoneyNotEnoughException异常
package com.st.bank.exceptions;/*** @author: TIKI* @Project: mybatis -MoneyNotEnoughException* @Pcakage: com.st.bank.exceptions.MoneyNotEnoughException* @Date: 2022年11月15日 20:52* @Description:*/
public class MoneyNotEnoughException extends Exception{public MoneyNotEnoughException() {}public MoneyNotEnoughException(String message) {super(message);}
}
TransferException异常
package com.st.bank.exceptions;/*** @author: TIKI* @Project: mybatis -TransferException* @Pcakage: com.st.bank.exceptions.TransferException* @Date: 2022年11月15日 21:03* @Description:转账异常*/
public class TransferException extends Exception{public TransferException() {}public TransferException(String message) {super(message);}
}
package com.st.bank.web;import com.st.bank.exceptions.MoneyNotEnoughException;
import com.st.bank.exceptions.TransferException;
import com.st.bank.service.AccountService;
import com.st.bank.service.impl.AccountServiceImpl;
import jakarta.servlet.ServletException;
import jakarta.servlet.annotation.WebServlet;
import jakarta.servlet.http.HttpServlet;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;import java.io.IOException;/*** @author: TIKI* @Project: mybatis -AccountServlet* @Pcakage: com.st.bank.web.AccountServlet* @Date: 2022年11月15日 15:55* @Description:*/
@WebServlet("/transfer")
public class AccountServlet extends HttpServlet {// 为了让变量在其他方法也能使用private AccountService accountService = new AccountServiceImpl();@Overrideprotected void doPost(HttpServletRequest request, HttpServletResponse response)throws ServletException, IOException {// 1.获取表单数据String fromActno = request.getParameter("fromActno");String toActno = request.getParameter("toActno");double money = Double.parseDouble(request.getParameter("money"));try {// 2.调用service的转账方法完成转账(调业务层)accountService.transfer(fromActno, toActno, money);// 3.调用视图层进行结果展示response.sendRedirect(request.getContextPath() + "/success.html");} catch (MoneyNotEnoughException e) {response.sendRedirect(request.getContextPath() + "/error1.html");} catch (TransferException e) {response.sendRedirect(request.getContextPath() + "/error2.html");}}
}
ThreadLocal:实际上是一下Map集合
package com.powernode.threadlocal;import java.util.HashMap;
import java.util.Map;/*** 自定义一个ThreadLocal类*/
public class MyThreadLocal {/*** 所有需要和当前线程绑定的数据要放到这个容器当中*/private Map map = new HashMap<>();/*** 向ThreadLocal中绑定数据*/public void set(T obj){map.put(Thread.currentThread(), obj);}/*** 从ThreadLocal中获取数据* @return*/public T get(){return map.get(Thread.currentThread());}/*** 移除ThreadLocal当中的数据*/public void remove(){map.remove(Thread.currentThread());}
}
为了保证service和dao中使用的SqlSession对象是同一个,可以将SqlSession对象存放到 ThreadLocal当中【保证一个线程对应一个SqlSession】
修改SqlSessionUtil工具类:将SqlSession对象存放到 ThreadLocal当中,并添加close函数
package com.st.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;/*** @author: TIKI* @Project: mybatis -SqlSessionUtil* @Pcakage: com.st.mybatis.utils.SqlSessionUtil* @Date: 2022年10月29日 14:53* @Description:MyBatis工具类*/
public class SqlSessionUtil {private SqlSessionUtil(){};// 工具类的构方法私有化,防止new对象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);}}private static ThreadLocal local = new ThreadLocal<>();/*** @return 返回一个SqlSession对象*/public static SqlSession openSession(){SqlSession sqlSession = local.get();if (sqlSession == null){sqlSession = sqlSessionFactory.openSession();// 将sqlSession对象绑定到当前线程local.set(sqlSession);}return sqlSession;}/** 关闭SqlSession对象(从当前线程中溢出SqlSession对象)* @param sqlSession*/public static void close(SqlSession sqlSession){if (sqlSession != null) {sqlSession.close();local.remove();}}
}
修改dao中的方法:AccountDaoImpl中所有方法中的提交commit和关闭close代码全部删除
package com.st.bank.dao.impl;import com.st.bank.dao.AccountDao;
import com.st.bank.pojo.Account;
import com.st.bank.utils.SqlSessionUtil;
import org.apache.ibatis.session.SqlSession;/*** @author: TIKI* @Project: mybatis -AccountDaoImpl* @Pcakage: com.st.bank.dao.impl.AccountDaoImpl* @Date: 2022年11月15日 16:12* @Description:*/
public class AccountDaoImpl implements AccountDao {@Overridepublic int updateAccount(Account account) {SqlSession sqlSession = SqlSessionUtil.openSession();int count = sqlSession.update("account.updateAccount",account);return count;}@Overridepublic Account selectByActno(String actno) {SqlSession sqlSession = SqlSessionUtil.openSession();Account account = (Account) sqlSession.selectOne("account.selectByActno", actno);return account;}
}
修改service中的代码:添加事务控制代码(提交事务 关闭事务)
package com.st.bank.service.impl;import com.st.bank.dao.AccountDao;
import com.st.bank.dao.impl.AccountDaoImpl;
import com.st.bank.exceptions.MoneyNotEnoughException;
import com.st.bank.exceptions.TransferException;
import com.st.bank.pojo.Account;
import com.st.bank.service.AccountService;
import com.st.bank.utils.SqlSessionUtil;
import org.apache.ibatis.session.SqlSession;/*** @author: TIKI* @Project: mybatis -AccountServiceImpl* @Pcakage: com.st.bank.service.impl.AccountServiceImpl* @Date: 2022年11月15日 16:03* @Description:*/
public class AccountServiceImpl implements AccountService {private AccountDao accountDao = new AccountDaoImpl();@Overridepublic void transfer(String fromActno, String toActno, double money) throws MoneyNotEnoughException, TransferException {// 添加事务控制代码SqlSession sqlSession = SqlSessionUtil.openSession();// 1. 判断转出账户的余额是否充足(select)Account fromAccount = accountDao.selectByActno(fromActno);if (fromAccount.getBalance() < money){// 2. 如果转出账户余额不足,提示用户exceptionthrow new MoneyNotEnoughException("对不起,余额不足");}// 3. 如果转出账户余额充足,更新转出账户余额(update)fromAccount.setBalance(fromAccount.getBalance() - money);int count = accountDao.updateAccount(fromAccount);// 模拟异常
// String s = null;
// s.toString();// 4. 更新转入账户余额(update)Account toAccount = accountDao.selectByActno(toActno);toAccount.setBalance(toAccount.getBalance() + money);count += accountDao.updateAccount(toAccount);if (count != 2) {throw new TransferException("转账失败");}// 提交事务sqlSession.commit();// 关闭事务sqlSession.close();}
}
引入Javassist依赖
org.javassist javassist 3.29.1-GA
生成第一个类
@Testpublic void testGenerateFirstClass() throws Exception{// 获取类池,这个类池就是用来给我生成class的ClassPool pool = ClassPool.getDefault();// 制造类(需要告诉javassist,类名是啥)CtClass ctClass = pool.makeClass("com.st.bank.dao.impl.AccountDaoImpl");// 制造方法String methodCode = "public void insert(){System.out.println(123);}";CtMethod ctMethod = CtMethod.make(methodCode, ctClass);// 将方法添加到类中ctClass.addMethod(ctMethod);// 在内存中生成classctClass.toClass();// 类加载到JVM当中,返回AccountDaoImpl类的字节码Class> clazz = Class.forName("com.st.bank.dao.impl.AccountDaoImpl");// 创建对象Object obj = clazz.newInstance();// 获取AccountDaoImpl中的insert方法Method insertMethod = clazz.getDeclaredMethod("insert");// 调用方法insertinsertMethod.invoke(obj);}
未解决
AccountDao接口
package com.st.bank.dao;/*** 账户的DAO对象,负责t_act表中数据的CRUD*/
public interface AccountDao {void delete();int insert(String actno);int update(String actno, Double balance);String selectByActno(String actno);
}
使用Javassist生成AccountDaoImpl类
@Testpublic void testGenerateAccountDaoImpl() throws Exception{// 获取类池ClassPool pool = ClassPool.getDefault();// 制造类CtClass ctClass = pool.makeClass("com.st.bank.dao.impl.AccountDaoImpl");// 制造接口CtClass ctInterface = pool.makeInterface("com.st.bank.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 balanceClass>[] 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");}
创建GenerateDaoProxy工具类,根据Dao接口,自动生成实现类的字节码文件
凡是使用GenerateDaoProxy的,SQLMapper.xml映射文件中namespace必须是dao接口的全名,id必须是dao接口中的方法名。
package com.st.bank.utils;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;/*** @author: TIKI* @Project: mybatis -GenerateDaoProxy* @Pcakage: com.st.bank.utils.GenerateDaoProxy* @Date: 2022年11月17日 20:24* @Description:工具类 可以动态的生成DAO的实现类(可以动态生成DAO的代理类)*/
public class GenerateDaoProxy {/*** 生成dao接口实现类,并且将实现类的对象创建出来并返回。* @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++) {Class> parameterType = parameterTypes[i];methodCode.append(parameterType.getName());methodCode.append(" ");methodCode.append("arg" + i);if(i != parameterTypes.length - 1){methodCode.append(",");}}methodCode.append(")");methodCode.append("{");// 需要方法体当中的代码// 包名需要完整包名methodCode.append("org.apache.ibatis.session.SqlSession sqlSession = com.st.bank.utils.SqlSessionUtil.openSession();");// 需要知道是什么类型的sql语句// sql语句的id是框架使用者提供的,具有多变性。对于我框架的开发人员来说。我不知道。// 既然我框架开发者不知道sqlId,怎么办呢?mybatis框架的开发者于是就出台了一个规定:// 凡是使用GenerateDaoProxy机制的。sqlId都不能随便写。namespace必须是dao接口的全限定名称。id必须是dao接口中方法名。String sqlId = daoInterface.getName() + "." + method.getName();// 获取sql语句的类型SqlCommandType sqlCommandType = sqlSession.getConfiguration().getMappedStatement(sqlId).getSqlCommandType();if (sqlCommandType == SqlCommandType.INSERT) {}if (sqlCommandType == SqlCommandType.DELETE) {}if (sqlCommandType == SqlCommandType.UPDATE) {// 与先前的代码相对应 arg0methodCode.append("return sqlSession.update(\""+sqlId+"\", arg0);");}if (sqlCommandType == SqlCommandType.SELECT) {String returnType = method.getReturnType().getName();methodCode.append("return ("+returnType+")sqlSession.selectOne(\""+sqlId+"\", arg0);");}methodCode.append("}");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;}
}
一般使用mybatis的话,一般不叫做XXXDao,叫做XXXMapper
使用代理机制实现xxxMapper接口的实现类
SQLMapper.xml映射文件中namespace必须是dao接口的全名,id必须是dao接口中的方法名。!!!!
// private AccountDao accountDao = new AccountDaoImpl();// 使用GenerateDaoProxy实现AccountDao
// private AccountDao accountDao = (AccountDao) GenerateDaoProxy.generate(SqlSessionUtil.openSession(),AccountDao.class);// 使用mybatis的代理类生成dao接口的实现类:在内存中生成dao接口的代理类,然后创建代理类的实例// 注意:SQLMapper.xml映射文件中namespace必须是dao接口的全名,id必须是dao接口中的方法名。!!!!private AccountDao accountDao = SqlSessionUtil.openSession().getMapper(AccountDao.class);
#{}的执行结果:
[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'
${}如果需要把SQL语句的关键字放到SQL语句中,只能使用${},因为#{}是以值的形式**‘值’**放到SQL语句当中的。
需求:通过向sql语句中注入asc或desc关键字,来完成数据的升序或降序排列。
#{}的执行结果:
Preparing: selectid, car_num as carNum, brand, guide_price as guidePrice, produce_time as produceTime, car_type as carTypefrom t_car order by produce_time ?
Parameters: asc(String)selectid, 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 carTypefrom t_car order by produce_time asc
Parameters:
向SQL语句当中拼接表名,就需要使用${}
现实业务当中,可能会存在分表存储数据的情况。因为一张表存的话,数据量太大。查询效率比较低。
可以将这些数据有规律的分表存储,这样在查询的时候效率就比较高。因为扫描的数据量变少了。
日志表:专门存储日志信息的。如果t_log只有一张表,这张表中每一天都会产生很多log,慢慢的,这个表中数据会很多。
怎么解决问题?
可以每天生成一个新表。每张表以当天日期作为名称,例如:
t_log_20220901
t_log_20220902
…
你想知道某一天的日志信息怎么办?
假设今天是20220901,那么直接查:t_log_20220901的表即可。
使用#{}会是这样:select * from t_log_‘20220901’
使用${}会是这样:select * from t_log_20220901
需求:查询奔驰系列的汽⻋。【只要品牌brand中含有奔驰两个字的都查询出来。】
sql语句写法
select * from t_car where brand like '%奔驰%';select * from t_car where brand like '%比亚迪%';
第一种方案:
select * from t_car where brand like ‘%${brand}%’
第二种方案:concat函数,这个是mysql数据库当中的一个函数,专门进行字符串拼接
select * from t_car where brand like concat(‘%’,#{brand},‘%’)
第三种方案:比较鸡肋了。可以不算。
select * from t_car where brand like concat(‘%’,‘${brand}’,‘%’)
第四种方案:
select * from t_car where brand like “%”#{brand}“%”
resultType属性用来指定查询结果集的封装类型,这个名字太⻓,可以起别名吗?可以。
在mybatis-config.xml文件中使用typeAliases标签来起别名,包括两种方式:
第一种方式:typeAlias
第二种方式:package
将这个包下的所有的类全部自动起别名,别名就是简类名,不区分大小写
mybatis-config.xml文件中的mappers标签。
mapper标签的作用是指定SqlMapper.xml文件的路径
SQL映射文件的配置方式包括四种:
resource:从类的根路径下开始查找资源。
url:从指定的全限定资源路径中 加载
class:使用映射器接口实现类的完全限定类名,必须带有包名的。
思考: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
package:将包内的映射器接⼝实现全部注册为映射器
要求同上
要求类的根路径下必须有:CarMapper.xml 要求在d:/下有CarMapper.xml文件
在File->Settings->Editor->File and Code Templates中添加模板
MyBatis核心配置文件 mybatis-config.xml
MyBatisSQL映射文件 XXXMapper.xml
前提:主键是⾃动生成的。
业务背景:插入一条新的记录之后,⾃动生成了主键,⽽这个主键需要在其他表中使用时。
代码
insert into t_car values(null,#{carNum},#{brand},#{guidePrice},#{produceTime},#{carType})
@Testpublic void testInsertCarUseGeneratedKeys(){SqlSession sqlSession = SqlSessionUtil.openSession();CarMapper mapper = sqlSession.getMapper(CarMapper.class);Car car = new Car(null,"9991", "凯美瑞", 30.0, "2020-11-11", "燃油车");mapper.insertCarUseGeneratedKeys(car);System.out.println(car);sqlSession.commit();sqlSession.close();}
Mapper接口中参数的问题
简单类型包括
简单类型对于mybatis来说都是可以自动类型识别的:
也就是说对于mybatis来说,它是可以自动推断出ps.setXxxx()方法的。ps.setString()还是 ps.setInt()。
完整代码
其中sql语句中的javaType,jdbcType,以及select标签中的parameterType属性,都是用来帮助 mybatis进行类型确定的。【可以省略】
parameterType属性:告诉mybatis框架,这个方法的参数类型是什么的
mybatis框架由自动推断机制[获取实际方法调用的参数类型],所以大部分情况下parameterType属性都是可以省略不写的
SQL语句最终是这样的:
select * from t_student where id = ?
JDBC代码是一定要给?传值的。
怎么传值?ps.setXxx(第几个问号, 传什么值);
ps.setLong(1, 1L);
ps.setString(1, “zhangsan”);
ps.setDate(1, new Date());
ps.setInt(1, 100);
…
mybatis底层到底调用setXxx的哪个方法,取决于parameterType属性的值。
如果参数只有一个的话,#{}里面的内容就随便写了。对于${}来说,注意加单引号。
这种方式是⼿动封装Map集合,将每个条件以key和value的形式存放到集合中。然后在使用的时候通过# {map集合的key}来取值。
代码
insert into t_student(id,name,age,sex,birth,height) values(null,#{姓名},#{年龄},#{性别},#{生日},#{身高})
@Testpublic void testInsertStudentByMap(){SqlSession sqlSession = SqlSessionUtil.openSession();StudentMapper mapper = sqlSession.getMapper(StudentMapper.class);Map map = new HashMap<>();map.put("姓名", "赵六");map.put("年龄", 20);map.put("身高", 1.81);map.put("性别", '男');map.put("生日", new Date());mapper.insertStudentByMap(map);sqlSession.commit();sqlSession.close();}
#{}里面写的是属性名字。这个属性名其本质上是:set/get方法名去掉set/get之后 的名字。
代码
insert into t_student(id,name,age,sex,birth,height) values(null,#{name},#{age},#{sex},#{birth},#{height})
@Testpublic void testInsertStudentByPOJO(){SqlSession sqlSession = SqlSessionUtil.openSession();StudentMapper mapper = sqlSession.getMapper(StudentMapper.class);// POJO对象Student student = new Student();student.setName("张飞");student.setAge(50);student.setSex('女');student.setBirth(new Date());student.setHeight(10.0);mapper.insertStudentByPOJO(student);sqlSession.commit();sqlSession.close();}
需求:使用多个参数一起进行查询
实现原理:实际上在mybatis底层会创建一个map集合,以arg0或者param1为key,以方法上的参数为 value
Map 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}
代码
@Testpublic void testSelectByNameAndSex(){SqlSession sqlSession = SqlSessionUtil.openSession();StudentMapper mapper = sqlSession.getMapper(StudentMapper.class);List students = mapper.selectByNameAndSex("张三", '男');students.forEach(student -> System.out.println(student));sqlSession.close();}
使用 @Param注解即可自定义map集合的key,可以增强可读性。
代码
StudentMapper接⼝
/*** 根据name和age查询 * value可以省略不写* @param name * @param age * @return*/List selectByNameAndAge(@Param(value="name") String name, @Param("age") int age);
@Test
public void testSelectByNameAndSex2(){SqlSession sqlSession = SqlSessionUtil.openSession();// mapper实际上指向了代理对象StudentMapper mapper = sqlSession.getMapper(StudentMapper.class);// mapper是代理对象// selectByNameAndSex2是代理方法List students = mapper.selectByNameAndSex2("张三", '男');students.forEach(student -> System.out.println(student));sqlSession.close();
}
代理模式
源码分析
select标签的returnType属性,用来指定返回结果的类型
mybatis为常见的Java类型内建的别名
select标签的resultMap属性,用来指定使用哪个结果映射。resultMap后面的值是resultMap的id
查询结果是一条的话,返回一个pojo对象
查询结果是一条的话可以使用List集合接收吗?当然可以。
接口
/*** 根据id查询Car信息* @param id* @return*/Car selectById(Long id);
mapper.xml
id,car_num as carNum,brand,guide_price as guidePrice,produce_time as produceTime,car_type as carType
test
@Testpublic void testSelectById(){SqlSession sqlSession = SqlSessionUtil.openSession();CarMapper mapper = sqlSession.getMapper(CarMapper.class);Car car = mapper.selectById(158L);System.out.println(car);sqlSession.close();}
当查询的记录条数是多条的时候,必须使用集合接收。
如果使用单个实体类接收会出现异常TooManyResultsException。
mapper接口
/*** 获取所有的Car* @return*/List selectAll();
mapper.xml
返回car
test
@Testpublic void testSelectAll(){SqlSession sqlSession = SqlSessionUtil.openSession();CarMapper mapper = sqlSession.getMapper(CarMapper.class);List cars = mapper.selectAll();cars.forEach(car -> System.out.println(car));sqlSession.close();}
当返回的数据,没有合适的实体类对应的话,可以采用Map集合接收。字段名做key,字段值做value。
查询如果可以保证只有一条数据,则返回一个Map集合即可。
接口
/*** 根据id获取汽车信息。将汽车信息放到Map集合中。* +-----+---------+----------+-------------+--------------+----------+* | id | car_num | brand | guide_price | produce_time | car_type |* +-----+---------+----------+-------------+--------------+----------+* | 158 | 1111 | 比亚迪汉 | 3.00 | 2000-10-10 | 新能源 |* +-----+---------+----------+-------------+--------------+----------+** Map* k v* -----------------------* "id" 158* "car_num" 1111* "brand" 比亚迪汉* ....** @param id* @return*/Map selectByIdRetMap(Long id);
xml
test
@Testpublic void testSelectByIdRetMap(){SqlSession sqlSession = SqlSessionUtil.openSession();CarMapper mapper = sqlSession.getMapper(CarMapper.class);Map car = mapper.selectByIdRetMap(158L);System.out.println(car);sqlSession.close();}
查询结果条数⼤于等于1条数据,则可以返回一个存储Map集合的List集合。List
mapper接口
@Testpublic void testSelectAllRetListMap(){SqlSession sqlSession = SqlSessionUtil.openSession();CarMapper mapper = sqlSession.getMapper(CarMapper.class);List
mapper.xml
map
test
@Testpublic void testSelectAllRetListMap(){SqlSession sqlSession = SqlSessionUtil.openSession();CarMapper mapper = sqlSession.getMapper(CarMapper.class);List
返回一个大的Map集合:拿Car的id做key,以后取出对应的Map集合时更方便。
mapper接口
/*** 查询所有的Car,返回一个大Map集合。* Map集合的key是每条记录的主键值。* Map集合的value是每条记录。* {* 160={car_num=3333, id=160, guide_price=32.00, produce_time=2000-10-10, brand=奔驰E300L, car_type=新能源},* 161={car_num=4444, id=161, guide_price=32.00, produce_time=2000-10-10, brand=奔驰C200, car_type=新能源},* 162={car_num=9999, id=162, guide_price=30.00, produce_time=2020-10-11, brand=帕萨特, car_type=燃油车},* 163={car_num=9991, id=163, guide_price=30.00, produce_time=2020-11-11, brand=凯美瑞, car_type=燃油车},* 158={car_num=1111, id=158, guide_price=3.00, produce_time=2000-10-10, brand=比亚迪汉, car_type=新能源},* 159={car_num=2222, id=159, guide_price=32.00, produce_time=2000-10-10, brand=比亚迪秦, car_type=新能源}* }* @return*/@MapKey("id") // 将查询结果的id值作为整个大Map集合的key。Map> selectAllRetMap();
mapper.xml
test
@Testpublic void testSelectAllRetMap(){SqlSession sqlSession = SqlSessionUtil.openSession();CarMapper mapper = sqlSession.getMapper(CarMapper.class);Map> map = mapper.selectAllRetMap();System.out.println(map);sqlSession.close();}
查询结果的列名和java对象的属性名对应不上怎么办?
第一种方式:as给列起别名
第二种方式:使用resultMap进行结果映射
第三种方式:是否开启驼峰命名自动映射(配置settings)
select标签的resultMap属性,用来指定使用哪个结果映射。
resultMap:指定数据库表的字段名和Java类的属性名的对应关系
type属性:用来指定POJO类的类名。
id属性:指定resultMap的唯一标识。这个id将来要在select标签中使用。
id标签:如果有主键,建议这里配置一个id标签,注意:这不是必须的。
result标签
association标签:关联,多对一
一个Student对象关联一个Clazz对象
分布查询时需使用的属性
collection标签:集合;一对多
一个Clazz对象关联一个Student对象
分布查询时需使用的属性名
代码
mapper接口
/*** 查询所有的Car信息。使用resultMap标签进行结果映射。* @return*/List selectAllByResultMap();
mapper.xml
test
@Testpublic void testSelectAllByResultMap(){SqlSession sqlSession = SqlSessionUtil.openSession();CarMapper mapper = sqlSession.getMapper(CarMapper.class);List cars = mapper.selectAllByResultMap();cars.forEach(car -> System.out.println(car));sqlSession.close();}
使用这种方式的前提是:属性名遵循Java的命名规范,数据库表的列名遵循SQL的命名规范。
在mybatis-config.xml文件中进行配置
if标签
代码
mapper接口
/*** 多条件查询* @param brand 品牌* @param guidePrice 指导价* @param carType 汽车类型* @return*/List selectByMultiCondition(@Param("brand") String brand, @Param("guidePrice") Double guidePrice, @Param("carType") String carType);
mapper.xml
test
@Testpublic void testSelectByMultiCondition(){SqlSession sqlSession = SqlSessionUtil.openSession();CarMapper mapper = sqlSession.getMapper(CarMapper.class);// 假设三个条件都不是空//List cars = mapper.selectByMultiCondition("比亚迪", 2.0, "新能源");// 假设三个条件都是空//List cars = mapper.selectByMultiCondition("", null, "");// 假设后两个条件不为空,第一个条件为空//List cars = mapper.selectByMultiCondition("", 2.0, "新能源");// 假设第一个条件不是空,后两个条件是空List cars = mapper.selectByMultiCondition("比亚迪", null, "");cars.forEach(car -> System.out.println(car));sqlSession.close();}
where标签的作用:让where子句更加动态智能。
代码
mapper接口
/*** 使用where标签,让where子句更加的智能。* @param brand* @param guidePrice* @param carType* @return*/List selectByMultiConditionWithWhere(@Param("brand") String brand, @Param("guidePrice") Double guidePrice, @Param("carType") String carType);
mapper.xml
test
@Testpublic void testSelectByMultiConditionWithWhere(){SqlSession sqlSession = SqlSessionUtil.openSession();CarMapper mapper = sqlSession.getMapper(CarMapper.class);// 三个条件都不是空//List cars = mapper.selectByMultiConditionWithWhere("比亚迪", 2.0, "新能源");// 三个条件都是空//List cars = mapper.selectByMultiConditionWithWhere("", null, "");// 如果第一个条件是空//List cars = mapper.selectByMultiConditionWithWhere("", 2.0, "新能源");// 后面两个条件是空List cars = mapper.selectByMultiConditionWithWhere("比亚迪", null, "");cars.forEach(car -> System.out.println(car));sqlSession.close();}
trim标签的属性:
代码
mapper接口
/*** 使用trim标签* @param brand* @param guidePrice* @param carType* @return*/List selectByMultiConditionWithTrim(@Param("brand") String brand, @Param("guidePrice") Double guidePrice, @Param("carType") String carType);
mapper.xml
test
@Testpublic void testSelectByMultiConditionWithTrim(){SqlSession sqlSession = SqlSessionUtil.openSession();CarMapper mapper = sqlSession.getMapper(CarMapper.class);List cars = mapper.selectByMultiConditionWithTrim("比亚迪", null, "");cars.forEach(car -> System.out.println(car));sqlSession.close();}
主要使用在update语句当中,用来生成set关键字,同时去掉最后多余的“,”
比如我们只更新提交的不为空的字段,如果提交的数据是空或者"",那么这个字段我们将不更新。
代码
mapper接口
/*** 使用set标签* @param car* @return*/int updateBySet(Car car);
mapper.xml
update t_carcar_num = #{carNum}, brand = #{brand}, guide_price = #{guidePrice}, produce_time = #{produceTime}, car_type = #{carType}, whereid = #{id}
不使用set标签
update t_car setcar_num = #{carNum},brand = #{brand},guide_price = #{guidePrice},produce_time = #{produceTime},car_type = #{carType}whereid = #{id}
test
@Testpublic void testUpdateBySet(){SqlSession sqlSession = SqlSessionUtil.openSession();CarMapper mapper = sqlSession.getMapper(CarMapper.class);Car car = new Car(158L, null,"丰田霸道",null,null,null);mapper.updateBySet(car);sqlSession.commit();sqlSession.close();}
语法格式:等同于 if elseif else
mapper接口
/*** 使用choose when otherwise标签。* @param brand* @param guidePrice* @param carType* @return*/List selectByChoose(@Param("brand") String brand, @Param("guidePrice") Double guidePrice, @Param("carType") String carType);
mapper.xml
test
@Testpublic void testSelectByChoose(){SqlSession sqlSession = SqlSessionUtil.openSession();CarMapper mapper = sqlSession.getMapper(CarMapper.class);// 三个条件都不为空//List cars = mapper.selectByChoose("丰田霸道",1.0,"新能源");// 第一个条件是空//List cars = mapper.selectByChoose(null,1.0,"新能源");// 前两个条件都是空//List cars = mapper.selectByChoose(null,null,"新能源");// 全部都是空List cars = mapper.selectByChoose(null,null,null);cars.forEach(car -> System.out.println(car));sqlSession.close();}
循环数组或集合,动态生成sql
mapper接口
/*** 批量删除。foreach标签* @param ids* @return*/int deleteByIds(@Param("ids") Long[] ids);
mapper.xml
delete from t_car where id in#{id}
delete from t_car whereid=#{id}
test
@Testpublic void testDeleteByIds(){SqlSession sqlSession = SqlSessionUtil.openSession();CarMapper mapper = sqlSession.getMapper(CarMapper.class);Long[] ids = {158L,159L,160L};int count = mapper.deleteByIds(ids);System.out.println(count);sqlSession.commit();sqlSession.close();}
mapper接口
/*** 批量插入,一次插入多条Car信息* @param cars* @return*/int insertBatch(@Param("cars") List cars);
mapper.xml
insert into t_car values(null,#{car.carNum},#{car.brand},#{car.guidePrice},#{car.produceTime},#{car.carType})
test
@Testpublic void testInsertBatch(){SqlSession sqlSession = SqlSessionUtil.openSession();CarMapper mapper = sqlSession.getMapper(CarMapper.class);Car car1 = new Car(null,"1200", "帕萨特1", 30.0, "2020-11-11", "燃油车");Car car2 = new Car(null,"1201", "帕萨特2", 30.0, "2020-11-11", "燃油车");Car car3 = new Car(null,"1202", "帕萨特3", 30.0, "2020-11-11", "燃油车");List cars = new ArrayList<>();cars.add(car1);cars.add(car2);cars.add(car3);mapper.insertBatch(cars);sqlSession.commit();sqlSession.close();}
代码
mapper接口
mapper.xml
id,car_num as carNum,brand,guide_price as guidePrice,produce_time as produceTime,car_type as carType
test
高级映射
怎么区分主表和副表?
谁在前谁是主表->JVM中的主对象
多对一
一对多
一对多的实现,通常是在一的一方中有List集合属性
比如在Clazz类中添加List
属性
许多学生对应一个班级
实现方式有多种,常见的包括三种:
第一种方式:一条SQL语句,级联属性映射
第二种方式:一条SQL语句,association标签。
在pojo类中添加副对象属性
编写sql语句:使用左外连接
与方式一区别在于resultMap,将关联对象使用association标签进行映射
association:翻译为关联。一个Student对象关联一个Clazz对象
第三种方式:两条SQL语句,分步查询。(这种方式常用:优点一是可复用。优点二是支持懒加载/延迟加载。)
方法:
分步查询的优点:
什么是延迟加载(懒加载),有什么用?
在mybatis当中怎么开启延迟加载呢?
association标签中添加fetchType=“lazy”
注意:默认情况下是没有开启延迟加载的。需要设置:fetchType=“lazy”
这种在association标签中配置fetchType=“lazy”,是局部的设置,只对当前的association关联的sql语句起作用。
在实际的开发中,大部分都是需要使用延迟加载的,所以建议开启全部的延迟加载机制:
在mybatis核心配置文件中添加全局配置:lazyLoadingEnabled=true
实际开发中的模式:
懒加载执行效果对比:如果需要使用到学生所在班级的名称,这个时候才会执行关联的sql语句
不使用cname
使用cname
代码
Student pojo类:添加clazz属性
package com.powernode.mybatis.pojo;import com.powernode.mybatis.mapper.ClazzMapper;/*** 学生信息*/
public class Student { // Student是多的一方private Integer sid;private String sname;private Clazz clazz; // Clazz是少的一方。@Overridepublic String toString() {return "Student{" +"sid=" + sid +", sname='" + sname + '\'' +", clazz=" + clazz +'}';}public Clazz getClazz() {return clazz;}public void setClazz(Clazz clazz) {this.clazz = clazz;}//省略其他get set...
}
StudentMapper接口
/*** 根据id获取学生信息。同时获取学生关联的班级信息。* @param id 学生的id* @return 学生对象,但是学生对象当中含有班级对象。*/
Student selectById(Integer id);
StudentMapper.xml
test
@Testpublic void testSelectById(){SqlSession sqlSession = SqlSessionUtil.openSession();StudentMapper mapper = sqlSession.getMapper(StudentMapper.class);Student student = mapper.selectById(1);System.out.println(student);sqlSession.close();}
代码
StudentMapper接口
/*** 一条SQL语句,association* @param id* @return*/Student selectByIdAssociation(Integer id);
StudentMapper.xml
test
@Testpublic void testSelectByIdAssociation(){SqlSession sqlSession = SqlSessionUtil.openSession();StudentMapper mapper = sqlSession.getMapper(StudentMapper.class);Student student = mapper.selectByIdAssociation(4);System.out.println(student);sqlSession.close();}
代码
StudentMapper接口
/*** 根据班级编号查询学生信息。* @param cid* @return*/
List selectByCidStep2(Integer cid);/*** 分部查询第一步:先根据学生的sid查询学生的信息。* @param sid* @return*/
Student selectByIdStep1(Integer sid);
StudentMapper.xml
ClazzMapper接口
/*** 分步查询第二步:根据cid获取班级信息。* @param cid* @return*/Clazz selectByIdStep2(Integer cid);
ClazzMapper.xml
StudentMapperTest
@Testpublic void testSelectByIdStep1(){SqlSession sqlSession = SqlSessionUtil.openSession();StudentMapper mapper = sqlSession.getMapper(StudentMapper.class);Student student = mapper.selectByIdStep1(5);//System.out.println(student);// 只需要看学生的名字System.out.println(student.getSname());// 程序执行到这里了,我想看看班级的名字//System.out.println(student.getClazz().getCname());sqlSession.close();}
代码
注意:实际开发中不能Student包含Clazz,Clazz包含Student,输出为null可以,不能两端都有值
案例:根据班级编号查询班级信息【班级中的学生编号及姓名】
一对多的实现,通常是在一的一方中有List集合属性。
在Clazz类中添加List
属性。
实现方式
collection
在主对象类中添加List<副对象>
属性
编写sql语句:使用左外连接
将集合对象使用collection标签进行映射
collection:翻译为集合。比如一个Clazz对象对应一个Student集合
分布查询
List<副对象>
属性clazz pojo类代码
package com.powernode.mybatis.pojo;import java.util.List;/*** 班级信息*/
public class Clazz {private Integer cid;private String cname;private List stus;@Overridepublic String toString() {return "Clazz{" +"cid=" + cid +", cname='" + cname + '\'' +", stus=" + stus +'}';}public List getStus() {return stus;}public void setStus(List stus) {this.stus = stus;}public Integer getCid() {return cid;}public void setCid(Integer cid) {this.cid = cid;}public String getCname() {return cname;}public void setCname(String cname) {this.cname = cname;}public Clazz() {}public Clazz(Integer cid, String cname) {this.cid = cid;this.cname = cname;}
}
代码
ClazzMapper接口
/*** 根据班级编号查询班级信息。* @param cid* @return*/Clazz selectByCollection(Integer cid);
ClazzMapper.xml
ClazzMapperTest
@Testpublic void testSelectByCollection(){SqlSession sqlSession = SqlSessionUtil.openSession();ClazzMapper mapper = sqlSession.getMapper(ClazzMapper.class);Clazz clazz = mapper.selectByCollection(1000);System.out.println(clazz);sqlSession.close();}
代码
ClazzMapper接口
/*** 分步查询。第一步:根据班级编号获取班级信息。* @param cid 班级编号* @return*/Clazz selectByStep1(Integer cid);
ClazzMapper.xml
StudentMapper接口
/*** 根据班级编号查询学生信息。* @param cid* @return*/List selectByCidStep2(Integer cid);
StudentMapper.xml
ClazzMapperTest
@Testpublic void testSelectByStep1(){SqlSession sqlSession = SqlSessionUtil.openSession();ClazzMapper mapper = sqlSession.getMapper(ClazzMapper.class);Clazz clazz = mapper.selectByStep1(1000);//System.out.println(clazz);// 只访问班级名字。System.out.println(clazz.getCname());// 只有用到的时候才会去执行第二步SQL//System.out.println(clazz.getStus());sqlSession.close();}
缓存:cache
缓存的作用:通过减少IO[读文件和写文件]的方式,来提高程序的执行效率。
mybatis的缓存:将select语句的查询结果放到缓存(内存)当中,下一次还是这条select语句的话,直
接从缓存中取,不再查数据库。
mybatis缓存包括:
范围:一级缓存小于二级缓存
一级缓存:将查询到的数据存储到SqlSession中。【针对一次会话】
二级缓存:将查询到的数据存储到SqlSessionFactory中。【只针对整个数据库】
全局性地开启或关闭所有映射器配置文件中已配置的任何缓存。默认就是true,无需设置。
或者集成其它第三方的缓存:比如EhCache【Java语言开发的】、Memcache【C语言开发的】
等。
缓存只针对于DQL语句,也就是说缓存机制只对应select语句。
常见的缓存技术
思考:什么时候不走缓存?
思考:什么时候一级缓存失效?
第一次DQL和第二次DQL之间你做了以下两件事中的任意一件,都会让一级缓存清空:
二级缓存的失效:只要两次查询之间出现了增删改操作。二级缓存就会失效。
思考:什么时候不走缓存?
思考:什么时候一级缓存失效?
CarMapper接口
/*** 根据id获取Car信息。* @param id* @return*/Car selectById(Long id);
CarMapper.xml
CarMapperTest
// 思考:什么时候不走缓存?// SqlSession对象不是同一个,肯定不走缓存。// 查询条件不一样,肯定也不走缓存。// 思考:什么时候一级缓存失效?// 第一次DQL和第二次DQL之间你做了以下两件事中的任意一件,都会让一级缓存清空:// 1. 执行了sqlSession的clearCache()方法,这是手动清空缓存。// 2. 执行了INSERT或DELETE或UPDATE语句。不管你是操作哪张表的,都会清空一级缓存。@Testpublic void testSelectById(){SqlSession sqlSession = SqlSessionUtil.openSession();CarMapper mapper1 = sqlSession.getMapper(CarMapper.class);Car car1 = mapper1.selectById(164L);System.out.println(car1);// 手动清空一级缓存//sqlSession.clearCache();// 在这里执行了INSERT DELETE UPDATE中的任意一个语句。并且和表没有关系。CarMapper mapper = sqlSession.getMapper(CarMapper.class);mapper.insertClazz(2000, "高三三班");CarMapper mapper2 = sqlSession.getMapper(CarMapper.class);Car car2 = mapper2.selectById(164L);System.out.println(car2);sqlSession.commit();sqlSession.close();}
@Testpublic void testSelectById() throws Exception{// 如果要获取不同的SqlSession对象,不能使用以下代码。//SqlSession sqlSession = SqlSessionUtil.openSession();SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(Resources.getResourceAsStream("mybatis-config.xml"));SqlSession sqlSession1 = sqlSessionFactory.openSession();SqlSession sqlSession2 = sqlSessionFactory.openSession();CarMapper mapper1 = sqlSession1.getMapper(CarMapper.class);Car car1 = mapper1.selectById(164L);System.out.println(car1);CarMapper mapper2 = sqlSession2.getMapper(CarMapper.class);Car car2 = mapper2.selectById(164L);System.out.println(car2);sqlSession1.close();sqlSession2.close();}
测试结果:同一个Session
二级缓存的失效:只要两次查询之间出现了增删改操作。二级缓存就会失效。【一级缓存也会失效】
二级缓存相关的配置[了解]
eviction:指定从缓存中移除某个对象的淘汰算法。默认采用LRU策略。
a. LRU:Least Recently Used。最近最少使用。优先淘汰在间隔时间内使用频率最低的对象。(其
实还有一种淘汰算法LFU,最不常用。)
b. FIFO:First In First Out。一种先进先出的数据缓存器。先进入二级缓存的对象最先被淘汰。
c. SOFT:软引用。淘汰软引用指向的对象。具体算法和JVM的垃圾回收算法有关。
d. WEAK:弱引用。淘汰弱引用指向的对象。具体算法和JVM的垃圾回收算法有关。
flushInterval:
a. 二级缓存的刷新时间间隔。单位毫秒。如果没有设置。就代表不刷新缓存,只要内存足够大,一
直会向二级缓存中缓存数据。除非执行了增删改。
readOnly::
a. true:多条相同的sql语句执行之后返回的对象是共享的同一个。性能好。但是多线程并发可能
会存在安全问题。
b. false:多条相同的sql语句执行之后返回的对象是副本,调用了clone方法。性能一般。但安
全。
size:
a. 设置二级缓存中最多可存储的java对象数量。默认值1024。
mapper接口
/*** 测试二级缓存* @param id* @return*/
Car selectById2(Long id);
mapper.xml
test
@Testpublic void testSelectById2() throws Exception{// 这里只有一个SqlSessionFactory对象。二级缓存对应的就是SqlSessionFactory。SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(Resources.getResourceAsStream("mybatis-config.xml"));SqlSession sqlSession1 = sqlSessionFactory.openSession();SqlSession sqlSession2 = sqlSessionFactory.openSession();CarMapper mapper1 = sqlSession1.getMapper(CarMapper.class);CarMapper mapper2 = sqlSession2.getMapper(CarMapper.class);// 这行代码执行结束之后,实际上数据是缓存到一级缓存当中了。(sqlSession1是一级缓存。)Car car1 = mapper1.selectById2(164L);System.out.println(car1);// 如果这里不关闭SqlSession1对象的话,二级缓存中还是没有数据的。// 如果执行了这行代码,sqlSession1的一级缓存中的数据会放到二级缓存当中。sqlSession1.close();// 这行代码执行结束之后,实际上数据会缓存到一级缓存当中。(sqlSession2是一级缓存。)Car car2 = mapper2.selectById2(164L);System.out.println(car2);// 程序执行到这里的时候,会将sqlSession1这个一级缓存中的数据写入到二级缓存当中。//sqlSession1.close();// 程序执行到这里的时候,会将sqlSession2这个一级缓存中的数据写入到二级缓存当中。sqlSession2.close();}
按照以下步骤操作,就可以完成集成:
引入mybatis整合ehcache的依赖。
org.mybatis.caches mybatis-ehcache 1.2.2
在类的根路径下新建echcache.xml文件,并提供以下配置信息。
修改SqlMapper.xml文件中的
标签,添加type属性。
编写测试程序使用
@Testpublic void testSelectById2() throws Exception{// 这里只有一个SqlSessionFactory对象。二级缓存对应的就是SqlSessionFactory。SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(Resources.getResourceAsStream("mybatis-config.xml"));SqlSession sqlSession1 = sqlSessionFactory.openSession();SqlSession sqlSession2 = sqlSessionFactory.openSession();CarMapper mapper1 = sqlSession1.getMapper(CarMapper.class);CarMapper mapper2 = sqlSession2.getMapper(CarMapper.class);// 这行代码执行结束之后,实际上数据是缓存到一级缓存当中了。(sqlSession1是一级缓存。)Car car1 = mapper1.selectById2(164L);System.out.println(car1);// 如果这里不关闭SqlSession1对象的话,二级缓存中还是没有数据的。// 如果执行了这行代码,sqlSession1的一级缓存中的数据会放到二级缓存当中。sqlSession1.close();// 这行代码执行结束之后,实际上数据会缓存到一级缓存当中。(sqlSession2是一级缓存。)Car car2 = mapper2.selectById2(164L);System.out.println(car2);// 程序执行到这里的时候,会将sqlSession1这个一级缓存中的数据写入到二级缓存当中。//sqlSession1.close();// 程序执行到这里的时候,会将sqlSession2这个一级缓存中的数据写入到二级缓存当中。sqlSession2.close();}
org.mybatis.generator mybatis-generator-maven-plugin 1.4.1 true mysql mysql-connector-java 8.0.30
该文件名必须叫做:generatorConfig.xml
该文件必须放在类的根路径下。
MyBatis3Simple版本
MyBatis3
双击运行即可,会自动生成pojo类、mapper接口、mapper.xml文件
MyBatis3Simple版本
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 {@Testpublic void testSelectAll(){SqlSession sqlSession = SqlSessionUtil.openSession();CarMapper mapper = sqlSession.getMapper(CarMapper.class);List cars = mapper.selectAll();cars.forEach(car -> System.out.println(car));sqlSession.close();}@Testpublic void testDeleteByPrimaryKey(){SqlSession sqlSession = SqlSessionUtil.openSession();CarMapper mapper = sqlSession.getMapper(CarMapper.class);int count = mapper.deleteByPrimaryKey(164L);System.out.println(count);sqlSession.commit();sqlSession.close();}}
MyBatis3
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 CarMapperTest {// CarExample类负责封装查询条件的。@Testpublic void testSelect(){SqlSession sqlSession = SqlSessionUtil.openSession();CarMapper mapper = sqlSession.getMapper(CarMapper.class);// 执行查询// 1. 查询一个Car car = mapper.selectByPrimaryKey(165L);System.out.println(car);// 2. 查询所有(selectByExample,根据条件查询,如果条件是null表示没有条件。)List 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));// 添加orcarExample.or().andCarTypeEqualTo("燃油车");// 执行查询List cars2 = mapper.selectByExample(carExample);cars2.forEach(car2 -> System.out.println(car2));sqlSession.close();}}
mysql的limit后面两个数字:
假设已知页码pageNum,还有每页显示的记录条数pageSize,第一个数字可以动态的获取吗?
所以,标准通用的mysql分页SQL:
select*
fromtableName ......
limit(pageNum - 1) * pageSize, pageSize
使用mybatis应该怎么做?
mapper接口
/*** 分页查询* @param startIndex 起始下标。* @param pageSize 每页显示的记录条数* @return*/List selectByPage(@Param("startIndex") int startIndex, @Param("pageSize") int pageSize);
mapper.xml
test
@Testpublic void testSelectByPage(){// 获取每页显示的记录条数int pageSize = 3;// 显示第几页:页码int pageNum = 3;// 计算开始下标int startIndex = (pageNum - 1) * pageSize;SqlSession sqlSession = SqlSessionUtil.openSession();CarMapper mapper = sqlSession.getMapper(CarMapper.class);List cars = mapper.selectByPage(startIndex, pageSize);cars.forEach(car -> System.out.println(car));sqlSession.close();}
获取数据不难,难的是获取分页相关的数据比较难。->插件
使用PageHelper插件进行分页,更加的便捷。
关键点:
com.github.pagehelper pagehelper 5.3.1
typeAliases标签下面进行配置:
关键点:
代码
mapper接口
/*** 查询所有的Car,通过分页查询插件PageHelper完成。* @return*/List selectAll();
mapper.xml
test
@Test
public void testSelectAll(){SqlSession sqlSession = SqlSessionUtil.openSession();CarMapper mapper = sqlSession.getMapper(CarMapper.class);// 一定一定一定要注意:在执行DQL语句之前。开启分页功能。int pageNum = 2;int pageSize = 3;PageHelper.startPage(pageNum, pageSize);List cars = mapper.selectAll();//cars.forEach(car -> System.out.println(car));// 封装分页信息对象new PageInfo()// PageInfo对象是PageHelper插件提供的,用来封装分页相关的信息的对象。PageInfo carPageInfo = new PageInfo<>(cars, 3);System.out.println(carPageInfo);sqlSession.close();/*PageInfo{pageNum=2, pageSize=3, size=3, startRow=4, endRow=6, total=7, pages=3,list=Page{count=true, pageNum=2, pageSize=3, startRow=3, endRow=6, total=7, pages=3, reasonable=false, pageSizeZero=false}[Car{id=168, carNum='1204', brand='奥迪Q7', guidePrice=3.0, produceTime='2009-10-11', carType='燃油车'},Car{id=169, carNum='1205', brand='朗逸', guidePrice=4.0, produceTime='2001-10-11', carType='新能源'},Car{id=170, carNum='1206', brand='奔驰E300L', guidePrice=50.0, produceTime='2003-02-03', carType='新能源'}],prePage=1, nextPage=3, isFirstPage=false, isLastPage=false, hasPreviousPage=true, hasNextPage=true,navigatePages=3, navigateFirstPage=1, navigateLastPage=3, navigatepageNums=[1, 2, 3]}*/
}
注解式开发方式
mybatis中也提供了注解式开发方式,采用注解可以减少Sql映射文件的配置。
使用注解式开发的话,sql语句是写在java程序中的,这种方式也会给sql语句的维护带来成本。
官方:
使用注解来映射简单语句会使代码显得更加简洁,但对于稍微复杂一点的语句,Java 注解不仅力不从心,还会
让你本就复杂的 SQL 语句更加混乱不堪。 因此,如果你需要做一些很复杂的操作,最好用 XML 来映射语句。
复杂的sql语句
原则:简单sql可以注解。复杂sql使用xml。
mapper接口
@Insert("insert into t_car values(null,#{carNum},#{brand},#{guidePrice},#{produceTime},#{carType})")
int insert(Car car);
test
@Test
public void testInsert(){SqlSession sqlSession = SqlSessionUtil.openSession();CarMapper mapper = sqlSession.getMapper(CarMapper.class);Car car = new Car(null,"6666","丰田霸道",32.0,"2020-11-11","燃油车");int count = mapper.insert(car);System.out.println(count);sqlSession.commit();sqlSession.close();
}
mapper接口
@Delete("delete from t_car where id = #{id}")
int deleteById(Long id);
test
@Test
public void testDeleteById(){SqlSession sqlSession = SqlSessionUtil.openSession();CarMapper mapper = sqlSession.getMapper(CarMapper.class);int count = mapper.deleteById(170L);System.out.println(count);sqlSession.commit();sqlSession.close();
}
mapper接口
@Update("update t_car set car_num=#{carNum},brand=#{brand},guide_price=#{guidePrice},produce_time=#{produceTime},car_type=#{carType} where id=#{id}")
int update(Car car);
test
@Test
public void testUpdate(){SqlSession sqlSession = SqlSessionUtil.openSession();CarMapper mapper = sqlSession.getMapper(CarMapper.class);Car car = new Car(165L,"6666","丰田霸道",32.0,"2020-11-11","燃油车");int count = mapper.update(car);System.out.println(count);sqlSession.commit();sqlSession.close();
}
mapper接口
需要开启驼峰命名自动映射
@Select("select * from t_car where id = #{id}")
@Results({@Result(property = "id", column = "id"),@Result(property = "carNum", column = "car_num"),@Result(property = "brand", column = "brand"),@Result(property = "guidePrice", column = "guide_price"),@Result(property = "produceTime", column = "produce_time"),@Result(property = "carType", column = "car_type")
})
Car selectById(Long id);
test
@Test
public void testSelectById(){SqlSession sqlSession = SqlSessionUtil.openSession();CarMapper mapper = sqlSession.getMapper(CarMapper.class);Car car = mapper.selectById(171L);System.out.println(car);sqlSession.close();
}