MyBatis 本是 apache 的一个开源项目 iBatis, 2010 年这个项目由 apache software foundation 迁移到了 google code,并且改名为MyBatis 。2013 年 11 月迁移到 Github。iBATIS 一词来源于“internet”和“abatis”的组合,是一个基于 Java 的持久层框架。iBATIS 提供的持久层框架包括 SQL Maps 和 Data Access Objects(DAO)。
Mybatis 基于java的持久层框架,它的内部封装了JDBC,让开发人员只需要关注SQL语句本身,不需要花费精力在驱动的加载、连接的创建、Statement的创建等复杂的过程。
Mybatis通过XML或注解的方式将要执行的各种的statement配置起来,并通过java对象和statement中的sql的动态参数进行映射生成最终执行的SQL语句,最后由mybatis框架执行SQL,并将结果直接映射为java对象。
采用了ORM思想解决了实体类和数据库表映射的问题。对JDBC进行了封装,屏蔽了JDBCAPI底层的访问细节,避免我们与jdbc的api打交道,就能完成对数据的持久化操作。
public class TestJDBC {
public static void main(String[] args) {
Connection conn = null;
PreparedStatement ps = null;
ResultSet rs = null;
try {
//加载驱动
Class.forName("com.mysql.cj.jdbc.Driver");
String url="jdbc:mysql://localhost:3306/mybatis?serverTimezone=GMT";
//获取连接
conn= DriverManager.getConnection(url,"root","root");
//SQL语句
String sql="select * from team;";
ps=conn.prepareStatement(sql);
//执行查询
rs = ps.executeQuery();
//遍历结果集
List<Team> list=new ArrayList<>();
while (rs.next()){
Team team=new Team();
team.setTeamName(rs.getString("teamName"));
team.setTeamId(rs.getInt("teamId"));
team.setCreateTime(rs.getDate("createTime"));
team.setLocation(rs.getString("location"));
list.add(team);
}
list.forEach(team -> System.out.println(team));
} catch (Exception e){
e.printStackTrace();
} finally {
try {
//关闭资源
if (rs != null){
rs.close();
}
if (ps != null){
ps.close();
}
if (conn != null){
conn.close();
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
CREATE TABLE `team` (
`teamId` int NOT NULL AUTO_INCREMENT COMMENT '球队ID',
`teamName` varchar(50) DEFAULT NULL COMMENT '球队名称',
`location` varchar(50) DEFAULT NULL COMMENT '球队位置',
`createTime` date DEFAULT NULL COMMENT '球队建立时间',
PRIMARY KEY (`teamId`)
) ENGINE=InnoDB AUTO_INCREMENT=1003 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
<dependencies>
<dependency>
<groupId>org.mybatisgroupId>
<artifactId>mybatisartifactId>
<version>3.5.6version>
dependency>
<dependency>
<groupId>mysqlgroupId>
<artifactId>mysql-connector-javaartifactId>
<version>8.0.23version>
dependency>
<dependency>
<groupId>junitgroupId>
<artifactId>junitartifactId>
<version>4.12version>
<scope>testscope>
dependency>
dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.pluginsgroupId>
<artifactId>maven-compiler-pluginartifactId>
<version>3.8.0version>
<configuration>
<source>1.8source>
<target>1.8target>
configuration>
plugin>
plugins>
build>
一般情况下:配置文件的名称可以自定义,课程中使用mybatis.xml。配置文件放置在java/resources中。
头文件去官网中复制粘贴。在这里给大家提供一个中文的网站。Mybatis网址
DOCTYPE configuration
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
<environments default="development">
<environment id="development">
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<property name="driver" value="com.mysql.cj.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://127.0.0.1:3306/mybatis? useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone=GMT"/>
<property name="username" value="root"/>
<property name="password" value="root"/>
dataSource>
environment>
environments>
configuration>
实体类中的属性必须与表中的列名保持一致
public class Team {
private Integer teamId;
private String teamName;
private String location;
private Date createTime;
//关系字段:一个球队可以拥有多个球员
//一方(球队)持有多方(球员)的集合
private List<Player> playerList1;
private List<Player> playerList2;
@Override
public String toString() {
return "Team{" +
"teamId=" + teamId +
", teamName='" + teamName + '\'' +
", location='" + location + '\'' +
", createTime=" + createTime +
", playerList1=" + playerList1 +
", playerList2=" + playerList2 +
'}';
}
//省略set get方法
}
我们是针对实体类Team.java和表Team进行ORM映射。
Mybatis框架中,ORM映射是针对SQL语句进行,Mybatis框架将SQL语句抽取到了XML中。所以我们需要针对每个实体类编写XML映射文件。
头文件在网站复制即可。
DOCTYPE configuration
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
<mapper namespace="com.kkb.pojo.Team">
<select id="queryAll" resultType="com.kkb.pojo.Team">
select * from team;
select>
mapper>
DOCTYPE configuration
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
<environments default="development">
<environment id="development">
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<property name="driver" value="${jdbc.driver}"/>
<property name="url" value="${jdbc.url}"/>
<property name="username" value="${jdbc.username}"/>
<property name="password" value="${jdbc.password}"/>
dataSource>
environment>
environments>
<mappers>
<mapper resource="com/kkb/pojo/Team.xml"/>
mappers>
configuration>
pom.xml文件配置映射文件的扫描路径
<build>
<resources>
<resource>
<directory>src/main/javadirectory>
<includes>
<include>**/*.propertiesinclude>
<include>**/*.xmlinclude>
includes>
<filtering>falsefiltering>
resource>
resources>
<plugins>
//省略
plugins>
build>
public class TestTeam {
private String resource="mybatis.xml"; //画的图纸
@Test
public void test01(){
SqlSession sqlSession=null;
try {
//读取配置文件
Reader reader = Resources.getResourceAsReader(resource);
//创建工厂
SqlSessionFactory factory=new SqlSessionFactoryBuilder().build(reader);//根据图纸创建出了工厂
//获取连接
sqlSession= factory.openSession();
//执行sql
List<Team> list = sqlSession.selectList("com.kkb.pojo.Team.queryAll");
//遍历结果
for (Team team : list) {
System.out.println(team);
}
} catch (IOException e) {
e.printStackTrace();
}finally {
//关闭资源
sqlSession.close();
}
}
}
Team.xml的映射文件中添加:
<select id="queryById" parameterType="java.lang.Integer" resultType="com.kkb.pojo.Team">
select * from team where teamId=#{id}
select>
测试类中添加如下内容:
@Test
public void testFindById(){
System.out.println("testFindById---------");
Team team = sqlSession.selectOne("com.kkb.pojo.Team.queryById", 1001);
System.out.println(team);
}
@Before //表示该方法在执行测试方法之前先执行
public void before(){
// System.out.println("before");
//读取配置文件
Reader reader = null;
try {
reader = Resources.getResourceAsReader(resource);
} catch (IOException e) {
e.printStackTrace();
}
//创建工厂
SqlSessionFactory factory=new SqlSessionFactoryBuilder().build(reader);//根据图纸创建出了工厂
//获取连接
sqlSession= factory.openSession();
}
@After//表示该方法在执行测试方法之后执行
public void after(){
//System.out.println("after");
sqlSession.close();
}
Team.xml的映射文件中添加:
<delete id="del" >
delete from team where teamId=#{id}
delete>
<update id="update" parameterType="com.kkb.pojo.Team">
update team set teamName=#{teamName},location=#{location}
where teamId=#{teamId}
update>
<insert id="add" parameterType="com.kkb.pojo.Team" >
INSERT INTO `team` (`teamName`, `location`, `createTime`)
VALUES (#{teamName}, #{location}, #{createTime})
insert>
测试类中添加如下方法:
@Test
public void testDel(){
int num = sqlSession.delete("com.kkb.pojo.Team.del", 1054);
sqlSession.commit();
System.out.println(num);
}
@Test
public void testUpdate(){
Team team=sqlSession.selectOne("com.kkb.pojo.Team.queryById",1053);
team.setTeamName("dengzeyang的球队");
team.setLocation("不来梅");
int num = sqlSession.update("com.kkb.pojo.Team.update", team);
sqlSession.commit();
System.out.println(num);
}
@Test
public void testAdd(){
Team team=new Team();
team.setTeamName("上海bilibili");
team.setLocation("上海");
team.setCreateTime(new Date());
int num = sqlSession.insert("com.kkb.pojo.Team.add", team);//增删改必须手动提交事务
sqlSession.commit();//手动提交事务
System.out.println(num);
}
Resources 类,顾名思义就是资源,用于读取资源文件。其有很多方法通过加载并解析资源文件,返回不同类型的 IO 流对象。
SqlSessionFactory 的 创 建 , 需 要 使 用 SqlSessionFactoryBuilder 对 象 的 build() 方 法 。 事实上使用SqlSessionFactoryBuilder的原因是将SqlSessionFactory这个复杂对象的创建交由Builder来执行,也就是使用了建造者设计模式。
建造者模式: 又称生成器模式,是一种对象的创建模式。 可以将一个产品的内部表象与产品的生成过程分割开来, 从而可以使一个建造过程生成具有 不同的内部表象的产品(将一个复杂对象的构建与它的表示分离, 使得同样的构建过程可以创建不同的表示). 这样用户只需指定需要建造的类型就可 以得到具体产品,而不需要了解具体的建造过程和细节.
在建造者模式中,角色分指导者(Director)与建造者(Builder): 用户联系指导者, 指导者指挥建造者, 最后得到产品. 建造者模式可以强制实行 一种分步骤进行的建造过程.
SqlSessionFactory 接口对象是一个重量级对象(系统开销大的对象),是线程安全的,所以一个应用只需要一个该对象即可。创建SqlSession 需要使用 SqlSessionFactory 接口的的 openSession()方法。
默认的 openSession()方法没有参数,它会创建有如下特性的 SqlSession:
- 会开启一个事务(也就是不自动提交)。
- 将从由当前环境配置的 DataSource 实例中获取 Connection 对象。事务隔离级别将会使用驱动或数据源的默认设置。
- 预处理语句不会被复用,也不会批量处理更新。
openSession(true):创建一个有自动提交功能的 SqlSession
openSession(false):创建一个非自动提交功能的 SqlSession,需手动提交
openSession():同 openSession(false)
SqlSession 接口对象用于执行持久化操作。一个 SqlSession 对应着一次数据库会话,一次会话以SqlSession 对象的创建开始,以SqlSession 对象的关闭结束。
SqlSession 接口对象是线程不安全的,所以每次数据库会话结束前,需要马上调用其 close()方法,将其关闭。再次需要会话,再次创建。SqlSession 在方法内部创建,使用完毕后关闭。
SqlSession 类中有超过 20 个方法,我们常用的几乎都是执行语法相关的方法。
这些方法被用来执行定义在 SQL 映射的 XML 文件中的 SELECT、INSERT、UPDATE 和 DELETE 语句。它们都会自行解释,每一句都使用语句的 ID 属性和参数对象,参数可以是原生类型(自动装箱或包装类)、JavaBean、POJO 或 Map。
<T> T selectOne(String statement, Object parameter)
<E> List<E> selectList(String statement, Object parameter)
<K,V> Map<K,V> selectMap(String statement, Object parameter, String mapKey)
int insert(String statement, Object parameter)
int update(String statement, Object parameter)
int delete(String statement, Object parameter)
<dependency>
<groupId>log4jgroupId>
<artifactId>log4jartifactId>
<version>1.2.17version>
dependency>
在resource下添加log4j.properties配置文件
# Global logging configuration info warning error
log4j.rootLogger=DEBUG,stdout
# Console output...
log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern=%5p [%t] - %m%n
<settings>
<setting name="logImpl" value="LOG4J"/>
settings>
ThreadLocal并非是一个线程的本地实现版本,它并不是一个Thread,而是threadlocalvariable(线程局部变量)。也许把它命名为ThreadLocalVar更加合适。线程局部变量(ThreadLocal)其实的功用非常简单,就是为每一个使用该变量的线程都提供一个变量值的副本, 是Java中一种较为特殊的线程绑定机制,是每一个线程都可以独立地改变自己的副本,而不会和其它线程的副本冲突。
示例:
class Test{
private ThreadLocal<String> str = new ThreadLocal<String>();
private List<String> list = new ArrayList<String>();
class A extends Thread {
public void run() {
str.set("dengzeyang");
System.out.println("A...." + str.get());
list.add("AAA");
System.out.println("A<<<"+list.get(0));
}
}
class B extends Thread {
public void run() {
System.out.println("B...." + str.get());
list.add("BBB");
System.out.println("B<<<"+list.get(0));
}
}
}
测试代码:
Test2 t=new Test2();
Test2.A a=t.new A();
Test2.B b=t.new B();
a.start();
b.start();
public class MybatisUtil {
private static ThreadLocal<SqlSession> sqlSessionThreadLocal=new ThreadLocal<>();
private static SqlSessionFactory factory;
static {
Reader reader = null;
try {
reader = Resources.getResourceAsReader("mybatis.xml");
} catch (IOException e) {
e.printStackTrace();
}
//创建工厂
factory=new SqlSessionFactoryBuilder().build(reader);//根据图纸创建出了工厂
}
/**
* 获取连接
* @return
*/
public static SqlSession getSqlSession(){
//从ThreadLocal中获取
SqlSession sqlSession = sqlSessionThreadLocal.get();
if(sqlSession==null) {
//创建sqlSession
sqlSession = factory.openSession();
//将sqlSession与线程进行绑定
sqlSessionThreadLocal.set(sqlSession);
}
return sqlSession;
}
/**
* 关闭连接
*/
public static void closeSqlSession(){
//从ThreadLocal中获取
SqlSession sqlSession = sqlSessionThreadLocal.get();
if(sqlSession!=null){
sqlSession.close();
sqlSessionThreadLocal.remove();
}
}
}
public interface TeamDao {
List<Team> queryAll();
Team queryById(Integer teamId);
int add(Team team);
int update(Team team);
int del(Integer teamId);
}
public class TeamDaoImpl implements TeamDao{
/**
* 查询所有球队
* @return
*/
@Override
public List<Team> queryAll() {
SqlSession sqlSession= MybatisUtil.getSqlSession();
return sqlSession.selectList("com.kkb.pojo.Team.queryAll");
}
/**
* 根据id查询单个球队
* @param teamId
* @return
*/
@Override
public Team queryById(Integer teamId) {
SqlSession sqlSession= MybatisUtil.getSqlSession();
return sqlSession.selectOne("com.kkb.pojo.Team.queryById",teamId);
}
/**
* 添加球队
* @param team
* @return
*/
@Override
public int add(Team team) {
SqlSession sqlSession= MybatisUtil.getSqlSession();
int num= sqlSession.insert("com.kkb.pojo.Team.add",team);
sqlSession.commit();
return num;
}
/**
* 更新球队
* @param team
* @return
*/
@Override
public int update(Team team) {
SqlSession sqlSession= MybatisUtil.getSqlSession();
int num= sqlSession.update("com.kkb.pojo.Team.update",team);
sqlSession.commit();
return num;
}
/**
* 根据id删除球队
* @param teamId
* @return
*/
@Override
public int del(Integer teamId) {
SqlSession sqlSession= MybatisUtil.getSqlSession();
int num= sqlSession.delete("com.kkb.pojo.Team.del",teamId);
sqlSession.commit();
return num;
}
}
测试:
public class TestTeamDao {
private TeamDao teamDao=new TeamDaoImpl();
@Test
public void testDel(){
int num = teamDao.del(1114);
System.out.println(num);
}
@Test
public void testUpdate(){
Team team = teamDao.queryById(1114);
team.setTeamName("lina");
team.setLocation("bj");
int num = teamDao.update(team);
System.out.println(num);
}
@Test
public void testAdd(){
Team team=new Team();
team.setTeamName("lina的球队");
team.setLocation("北京");
team.setCreateTime(new Date());
int num = teamDao.add( team);
System.out.println(num);
}
@Test
public void test02(){
Team team = teamDao.queryById(1001);
System.out.println(team);
}
@Test
public void test01(){
List<Team> teams = teamDao.queryAll();
teams.forEach(team -> System.out.println(team));
}
}
在前面例子中自定义 Dao 接口实现类时发现一个问题:Dao 的实现类其实并没有干什么实质性的工作,它仅仅就是通过 SqlSession 的相关API 定位到映射文件 mapper 中相应 id 的 SQL 语句,真正对 DB 进行操作的工作其实是由框架通过 mapper 中的 SQL 完成的。
所以,MyBatis 框架就抛开了 Dao 的实现类,直接定位到映射文件 mapper 中的相应 SQL 语句,对DB 进行操作。这种对 Dao 的实现方式称为Mapper接口的动态代理方式。
Mapper 动态代理方式无需程序员实现 Dao 接口。接口是由 MyBatis 结合映射文件自动生成的动态代理实现的。
public interface TeamMapper {
List<Team> queryAll();
Team queryById(Integer teamId);
int add(Team team);
int update(Team team);
int del(Integer teamId);
}
创建TeamMapper.xml文件,与Team.xml内容几乎一样,只有namespace="com.kkb.mapper.TeamMapper"修改为接口的完全限定名
DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.kkb.mapper.TeamMapper">
mapper>
在mybatis.xml配置文件中注册映射文件
<mappers>
<mapper resource="com/kkb/mapper/TeamMapper.xml"/>
mappers>
只需调用 SqlSession 的 getMapper()方法,即可获取指定接口的实现类对象。
public class TeamMapperTest {
private SqlSession sqlSession=MybatisUtil.getSqlSession();
@Test
public void test01(){
TeamMapper teamDao= sqlSession.getMapper(TeamMapper.class);
//添加
Team team=new Team();
team.setTeamName("勇士");
team.setLocation("金州");
team.setCreateTime(new Date());
int num=teamDao.add(team);
sqlSession.commit();//必须提交才能让增删改生效
System.out.println(num);
//更新
Team team1 = teamDao.queryById(1001);
team1.setTeamName("lina的球队");
num = teamDao.update(team1);
sqlSession.commit();//必须提交才能让增删改生效
System.out.println(num);
//删除
num=teamDao.del(1001);
sqlSession.commit();//必须提交才能让增删改生效
System.out.println(num);
//查询所有
List<Team> teams = teamDao.queryAll();
teams.forEach(t-> System.out.println(t));
}
}
CREATE TABLE `gamerecord` (
`recordId` varchar(36) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL,
`homeTeamId` int DEFAULT NULL COMMENT '主队ID',
`gameDate` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '比赛日期',
`score` int DEFAULT NULL COMMENT '得分',
`visitingTeamId` int DEFAULT NULL COMMENT '客队ID',
PRIMARY KEY (`recordId`),
KEY `homeTeamId` (`homeTeamId`),
KEY `visitingTeamId` (`visitingTeamId`),
CONSTRAINT `gamerecord_ibfk_1` FOREIGN KEY (`homeTeamId`) REFERENCES `team` (`teamId`),
CONSTRAINT `gamerecord_ibfk_2` FOREIGN KEY (`visitingTeamId`) REFERENCES `team` (`teamId`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
/**
* 比赛记录的实体类
*/
public class GameRecord {
private String recordId;
private Integer homeTeamId;
private Date gameDate;
private Integer score;
private Integer visitingTeamId;
//省略set get
}
public interface GameRecordMapper {
int add(GameRecord record);
}
添加GameRecordMapper.xml文件
DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.kkb.mapper.GameRecordMapper">
<insert id="add" parameterType="com.kkb.pojo.GameRecord">
<selectKey keyProperty="recordId" order="BEFORE" resultType="java.lang.String">
select UUID()
selectKey>
INSERT INTO `mybatis`.`gamerecord` (`recordId`, `homeTeamId`, `score`, `visitingTeamId`)
VALUES (#{recordId}, #{homeTeamId}, #{score}, #{visitingTeamId})
insert>
mapper>
修改TeamMapper.xml文件中的insert节点
<insert id="add" parameterType="com.kkb.pojo.Team" >
<selectKey keyProperty="teamId" order="AFTER" resultType="java.lang.Integer">
select LAST_INSERT_ID()
selectKey>
INSERT INTO `team` (`teamName`, `location`, `createTime`)
VALUES (#{teamName}, #{location}, #{createTime})
insert>
public class TestGameRecordMapper {
private SqlSession sqlSession=MybatisUtil.getSqlSession();
@Test
public void testAdd(){
GameRecordMapper mapper = MybatisUtil.getSqlSession().getMapper(GameRecordMapper.class);
GameRecord record=new GameRecord();
record.setHomeTeamId(1003);
record.setVisitingTeamId(1001);
record.setScore(90);
int add = mapper.add(record);
MybatisUtil.getSqlSession().commit(); //必须提交才能让增删改生效
System.out.println(add);
}
}
parameterType:接口中方法参数的类型,类型必须是完全限定名或别名(稍后讲别名)。该属性非必须,因为Mybatis框架能自行判断具体传入语句的参数,默认值为未设置(unset)。
TeamMapper.xml配置文件中添加如下:
<select id="queryByRange1" resultType="com.kkb.pojo.Team">
select * from team
where teamId>=#{param1} and teamId<=#{param2}
select>
测试类添加方法:
public class TestTeamMapperArg {
private TeamMapper teamMapper= MybatisUtil.getSqlSession().getMapper(TeamMapper.class);
@Test
public void test01(){
List<Team> teams = teamMapper.queryByRange1(1004, 1010);
teams.forEach(team -> System.out.println(team));
}
}
在方法的形参前面加入@Param(“自定义参数名称”),mapper文件中使用#{自定义参数名称}的方式传参。
TeamMapper接口添加如下内容:
List<Team> queryByRange2(@Param("min") Integer min, @Param("max") Integer max);
TeamMapper.xml配置文件中添加如下:
<select id="queryByRange2" resultType="com.kkb.pojo.Team">
select * from team
where teamId>=#{min} and teamId<=#{max}
select>
测试类添加方法:
@Test
public void test02(){
List<Team> teams = teamMapper.queryByRange2(1005, 1011);
teams.forEach(team -> System.out.println(team));
}
Map 集合可以存储多个值,使用Map向 mapper 文件一次传入多个参数。Map 集合使用 String的 key,Object 类型的值存储参数。 mapper 文件使用 # { key } 引用参数值TeamMapper接口添加如下内容:
List<Team> queryByRange3(Map<String,Object> map);
TeamMapper.xml配置文件中添加如下:
<select id="queryByRange3" resultType="com.kkb.pojo.Team">
select * from team
where teamId>=#{min} and teamId<=#{max}
select>
测试类添加方法:
@Test
public void test03(){
Map<String, Object> map=new HashMap<>();
map.put("min",1010);
map.put("max",1015);
List<Team> teams = teamMapper.queryByRange3(map);
teams.forEach(team -> System.out.println(team));
}
与map传递多个参数类似,要求映射文件中的参数占位符必须和pojo类中的属性完全一致。
实体类:
public class QueryVO {
private String name;
private Integer min;
private Integer max;
private String location;
//省略set get
}
TeamMapper接口添加如下内容:
List<Team> queryByCondition(QueryVO vo);
TeamMapper.xml配置文件中添加如下:
<select id="queryByCondition" resultType="com.kkb.pojo.Team">
select * from team
where teamId>=#{min} and teamId<=#{max}
and teamName like #{name} and location=#{location}
select>
测试类添加方法:
@Test
public void test04(){
QueryVO vo=new QueryVO();
vo.setLocation("洛杉矶");
vo.setName("%球队%");
vo.setMin(1001);
vo.setMax(1111);
List<Team> teams = teamMapper.queryByCondition(vo);
teams.forEach(team -> System.out.println(team));
}
#{}:表示一个占位符,通知Mybatis 使用实际的参数值代替。并使用 PrepareStatement 对象执行 sql 语句, #{…}代替sql 语句的“?”。这个是Mybatis 中的首选做法,安全迅速。
<select id="queryById" parameterType="java.lang.Integer" resultType="com.kkb.pojo.Team">
select * from team where teamId=#{id}
select>
表示字符串原样替换,通知Mybatis 使用美元符号包含的“字符串”替换所在位置。使用 Statement或者PreparedStatement 把 sql 语句和${}的内容连接起来。一般用在替换表名,列名,不同列排序等操作。
例如:根据球队名称,球队位置查询球队列表
方式1:
TeamMapper接口添加如下内容:
List<Team> queryByName(String teamName);
List<Team> queryByLocation(String location);
TeamMapper.xml配置文件中添加如下:
<select id="queryByName" resultType="team">
select * from team where teamName=#{teamName}
select>
<select id="queryByLocation" resultType="com.kkb.pojo.Team">
select * from team where location=#{location}
select>
测试类添加方法:
@Test
public void test05(){
System.out.println("根据球队名称查询:");
List<Team> teams = teamMapper.queryByName("zeyang的球队");
teams.forEach(team -> System.out.println(team));
System.out.println("根据球队位置查询:");
List<Team> teams2 = teamMapper.queryByLocation("洛杉矶");
teams2.forEach(team -> System.out.println(team));
}
方式2:使用不同列作为查询条件
TeamMapper接口添加如下内容:
List<Team> queryByFiled(@Param("column") String column,@Param("columnValue") String columnValue);
TeamMapper.xml配置文件中添加如下:
<select id="queryByFiled" resultType="com.kkb.pojo.Team">
select * from team where ${column}=#{columnValue}
select>
测试类添加方法:
@Test
public void test06(){
System.out.println("根据球队名称查询:");
List<Team> teams = teamMapper.queryByFiled("teamName","lina的球队");
teams.forEach(team -> System.out.println(team));
System.out.println("根据球队位置查询:");
List<Team> teams2 = teamMapper.queryByFiled("location","洛杉矶");
teams2.forEach(team -> System.out.println(team));
}
resultType: 执行 sql 得到 ResultSet 转换的类型,使用类型的完全限定名或别名。如果返回的是集合,设置的是集合元素的类型,而不是集合本身。resultType 和 resultMap,不能同时使用。
案例:返回球队的总记录数
TeamMapper接口添加如下内容:
int getCount();
TeamMapper.xml配置文件中添加如下:
<select id="getCount" resultType="int">
select count(teamId)from team
select>
测试类添加方法:
@Test
public void test07(){
int count = teamMapper.getCount();
System.out.println("总共的行数:"+count);
}
案例:参考之前的查询所有球队信息
List<Team> queryAll();
<select id="queryAll" resultType="com.kkb.pojo.Team">
select * from team
select>
当我们只需要查询表中几列数据的时候可以将sql的查询结果作为Map的key和value。一般使用的是Map
Map 作为接口返回值,sql 语句的查询结果最多只能有一条记录。大于一条记录会抛出TooManyResultsException异常。
如果有多行,使用List
案例:根据id查询球队名称和位置。
TeamMapper接口添加如下内容:
Map<Object,Object> queryTwoColumn(int teamId);
List<Map<Object,Object>> queryTwoColumnList();
TeamMapper.xml配置文件中添加如下:
<select id="getTwoColumn" resultType="hashmap">
select count(teamId) as 'sum',max(teamId) as 'max' from team
select>
<select id="getTwoColumnList" resultType="java.util.HashMap">
select count(teamId) as 'sum',max(teamId) as 'max' from team group by location;
select>
测试类添加方法:
@Test
public void test08(){
Map<String, Object> map = teamMapper.getTwoColumn();
System.out.println(map);
}
@Test
public void test09(){
List<Map<String, Object>> list = teamMapper.getTwoColumnList();
for (Map<String, Object> map : list) {
System.out.println(map);
}
}
resultMap 可以自定义 sql 的结果和 java 对象属性的映射关系。更灵活的把列值赋值给指定属性。
常用在列名和 java 对象属性名不一样的情况。
使用方式:
List<Team> queryAll2();
TeamMapper.xml映射文件添加:
<select id="queryAll2" resultMap="baseResultMap">
select * from team;
select>
<resultMap id="baseResultMap" type="com.kkb.pojo.Team">
<id column="teamId" property="teamId" javaType="java.lang.Integer" >id>
<result column="teamName" property="teamName" javaType="java.lang.String">result>
<result column="location" property="location" javaType="java.lang.String">result>
<result column="createTime" property="createTime" javaType="java.util.Date">result>
resultMap>
测试:
@Test
public void test10(){
List<Team> teams = teamMapper.queryAll2();
teams.forEach(team-> System.out.println(team));
}
案例准备工作:创建表:
use mybatis;
DROP TABLE IF EXISTS `users`;
CREATE TABLE `users` (
`user_id` int NOT NULL AUTO_INCREMENT COMMENT '用户id',
`user_name` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '用户姓名',
`user_age` int NULL DEFAULT NULL COMMENT '用户年龄',
PRIMARY KEY (`user_id`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci ROW_FORMAT = Dynamic;
INSERT INTO `users` VALUES (1, '贾宝玉', 14);
INSERT INTO `users` VALUES (2, '林黛玉', 13);
INSERT INTO `users` VALUES (3, '薛宝钗', 15);
SET FOREIGN_KEY_CHECKS = 1;
实体类Users.java
public class Users {
private Integer userId;
private String userName;
private Integer userAge;
@Override
public String toString() {
return "Users{" +
"userId=" + userId +
", userName='" + userName + '\'' +
", userAge=" + userAge +
'}';
}
}
接口UsersMapper.java
public interface UsersMapper {
Users queryById(int userId);
}
映射文件UsersMapper.xml
DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.kkb.mapper.UsersMapper">
<select id="queryById" resultType="com.kkb.pojo.Users">
select user_id as userId,user_name as userName,user_age as userAge from users where user_id=#{id};
select>
mapper>
测试类TestUsersMapper.java
public class TestUsersMapper {
private UsersMapper mapper= MybatisUtil.getSqlSession().getMapper(UsersMapper.class);
@Test
public void test1(){
Users user = mapper.queryById(1);
System.out.println(user);
}
}
接口UsersMapper.java添加方法
Users queryByID2(int userId);
映射文件UsersMapper.xml添加如下内容:
<select id="queryById2" resultMap="baseMap">
select * from users where user_id=#{id};
select>
<resultMap id="baseMap" type="com.kkb.pojo.Users">
<id column="user_id" property="userId"/>
<result column="user_name" property="userName"/>
<result column="user_age" property="userAge"/>
resultMap>
测试:
@Test
public void test2(){
Users user = mapper.queryById2(1);
System.out.println(user);
}
案例中使用的 mybatis.xml就是Mybatis的全局配置文件。
全局配置文件需要在头部使用约束文件
DOCTYPE configuration
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
MyBatis 的配置文件包含了会深深影响 MyBatis 行为的设置和属性信息。 配置文档的顶层结构如下:
configuration(配置)
properties--属性:加载外部的配置文件,例如加载数据库的连接信息
Settings--全局配置参数:例如日志配置
typeAliases--类型别名
typeHandlers----类型处理器
objectFactory-----对象工厂
Plugins------插件:例如分页插件
Environments----环境集合属性对象
environment(环境变量)
transactionManager(事务管理器)
dataSource(数据源)
Mappers---映射器:注册映射文件用
属性可以在外部进行配置,并可以进行动态替换。我们既可以在 properties 元素的子元素中设置(例如DataSource节点中的properties节点),也可以在 Java 属性文件中配置这些属性。
数据源中有连接数据库的四个参数数据,我们一般都是放在专门的属性文件中,mybatis的全局配置文件直接从属性文件中读取数据即可。
jdbc.driver=com.mysql.cj.jdbc.Driver
jdbc.url=jdbc:mysql://127.0.0.1:3306/mybatis?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone=GMT
jdbc.username=root
jdbc.password=123456
<properties resource="jdbc.properties"/>
<dataSource type="POOLED">
<!--创建数据源必备4大参数-->
<property name="driver" value="${jdbc.driver}"/>
<property name="url" value="${jdbc.url}"/>
<property name="username" value="${jdbc.username}"/>
<property name="password" value="${jdbc.password}"/>
</dataSource>
MyBatis 中极为重要的调整设置,它们会改变 MyBatis 的运行时行为.例如我们配置的日志就是应用之一。其余内容参考设置文档。
<settings>
<setting name="logImpl" value="LOG4J"/>
settings>
类型别名可为 Java 类型设置一个缩写名字。 它仅用于 XML 配置,意在降低冗余的全限定类名书写。
下面是一些为常见的 Java 类型内建的类型别名。它们都是不区分大小写的,注意,为了应对原始类型的命名重复,采取了特殊的命名风格。
<typeAliases>
<typeAlias type="com.kkb.pojo.Team" alias="Team">typeAlias>
<package name="com.kkb.pojo"/>
<package name="com.kkb.mapper"/>
typeAliases>
配置有多种方式:
语法:<mapper resource=""/>
使用相对于类路径的资源,从 classpath 路径查找文件
例如:<mapper resource="com/kkb/mapper/TeamMapper.xml" />
语法:<mapper class=""/>
使用的mapper接口的完全限定名
要求:接口和映射文件同包同名
例如<mapper class="com.kkb.mapper.GameRecordMapper"/>
语法:<package name=""/>
指定包下的所有Mapper接口
如:<package name="com.kkb.mapper"/>
注意:此种方法要求 Mapper接口名称和 mapper 映射文件名称相同,且在同一个目录中。
Mybatis 中访问数据库支持连接池技术,而且是采用的自己的连接池技术。在 Mybatis 的 mybatis.xml配置文件中,通过来实现 Mybatis 中连接池的配置。MyBatis 在初始化时,根据的 type 属性来创建相应类型的的数据源 DataSource。
Mybatis 的数据源分为三类:
前两个数据源都实现javax.sql.DataSource接口
Mybatis 框架是对 JDBC 的封装,所以 Mybatis 框架的事务控制方式,本身也是用 JDBC 的Connection对象的 commit(), rollback() .Connection 对象的 setAutoCommit()方法来设置事务提交方式的。自动提交和手工提交
<transactionManager type="JDBC"/>
该标签用于指定 MyBatis所使用的事务管理器。MyBatis 支持两种事务管理器类型:JDBC 与 MANAGED。
JDBC:使用JDBC的事务管理机制,通过Connection对象的 commit()方法提交,通过rollback()方法 回滚。默认情况下,mybatis将自动提交功能关闭了,改为了手动提交,观察日志可以看出,所以我们在程序中都需要自己提交事务或者回滚事务。
MANAGED:由容器来管理事务的整个生命周期(如Spring容器)。
SqlSessionFactory的openSession方法由重载,可以设置自动提交的方式。
如果sqlSession = SqlSessionFactory.openSession(true);参数设置为true,再次执行增删改的时候就不需要执行session.commit()方法,事务会自动提交。
public class Player {
private Integer playerId;
private String playerName;
private Integer playerNum;
private Integer teamId;
//关系字段:多个球员可以属于同一个球队
//多方(球员)持有一方(球队)的对象
private Team team1;
private Team team2;
private Team team3;
}
public interface PlayerMapper {
Player queryById(int playerId);
Player queryById1(int playerId);
Player queryById2(int playerId);
Player queryById3(int playerId);
List<Player> queryByTeamId(int teamId);
}
要求:两表的连接查询
要求:
- 两表的连接查询
- 关联对象中已经存在被引用的resultMap
要求:
- 不需要两表的连接查询
- 关联对象中已经存在被引用的查询方法
DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.kkb.mapper.PlayerMapper">
<select id="queryByTeamId" resultMap="baseResultMap">
select * from player where teamId=#{id}
select>
<select id="queryById" resultMap="baseResultMap">
select * from player where playerId=#{id}
select>
<resultMap id="baseResultMap" type="Player">
<id column="playerId" property="playerId"/>
<result column="playerName" property="playerName"/>
<result column="playerNum" property="playerNum"/>
<result column="teamId" property="teamId"/>
resultMap>
<select id="queryById1" resultMap="joinTeamResult1">
SELECT * FROM `player` p INNER JOIN team t
on t.teamId=p.teamId
where playerid=#{id}
select>
<resultMap id="joinTeamResult1" type="Player" extends="baseResultMap">
<result column="teamId" property="team1.teamId">result>
<result column="teamName" property="team1.teamName">result>
<result column="location" property="team1.location">result>
<result column="createTime" property="team1.createTime">result>
resultMap>
<select id="queryById2" resultMap="joinTeamResult2">
SELECT * FROM `player` p INNER JOIN team t
on t.teamId=p.teamId
where playerid=#{id}
select>
<resultMap id="joinTeamResult2" type="Player" extends="baseResultMap">
<association property="team2" javaType="Team"
resultMap="com.kkb.mapper.TeamMapper.baseResultMap"/>
resultMap>
<select id="queryById3" resultMap="joinTeamResult3">
select * from player where playerId=#{id}
select>
<resultMap id="joinTeamResult3" type="Player" extends="baseResultMap">
<association property="team3" javaType="Team"
select="com.kkb.mapper.TeamMapper.queryById" column="teamId"/>
resultMap>
mapper>
public class TestPlayerMapper {
private PlayerMapper playerMapper= MybatisUtil.getSqlSession().getMapper(PlayerMapper.class);
private TeamMapper teamMapper=MybatisUtil.getSqlSession().getMapper(TeamMapper.class);
@Test
public void test1(){
Player player = playerMapper.queryById(1);
System.out.println(player);
}
@Test
public void test2(){
Player player = playerMapper.queryById1(1);
System.out.println(player);
}
@Test
public void test3(){
Player player = playerMapper.queryById2(1);
System.out.println(player);
}
}
修改实体类Team.java:
public class Team implements Serializable {
private Integer teamId;
private String teamName;
private String location;
private Date createTime;
//关系字段:一个球队可以拥有多个球员
//一方(球队)持有多方(球员)的集合
private List<Player> playerList1;
private List<Player> playerList2;
}
TeamMapper.java接口中添加方法:
public interface TeamMapper {
Team queryById1(Integer teamId);
Team queryById2(Integer teamId);
}
PlayerMapper.java接口中添加方法:
public interface PlayerMapper {
List<Player> queryByTeamId(int teamId);
}
TeamMapper.xml添加
<select id="queryById1" resultMap="joinPlayMap1">
select * from team t inner join player p
on t.teamId=p.teamId where t.teamId=#{id}
select>
<resultMap id="joinPlayMap1" type="Team" extends="baseResultMap">
<collection property="playerList1" javaType="arraylist" ofType="Player"
resultMap="com.kkb.mapper.PlayerMapper.baseResultMap"/>
resultMap>
<select id="queryById2" resultMap="joinPlayMap2">
select * from team where teamId=#{id}
select>
<resultMap id="joinPlayMap2" type="Team" extends="baseResultMap">
<collection property="playerList2" javaType="arraylist" ofType="Player"
select="com.kkb.mapper.PlayerMapper.queryByTeamId" column="teamId"/>
resultMap>
PlayerMapper.xml添加如下内容:
<select id="queryByTeamId" resultMap="baseResultMap">
select * from player where teamId=#{id}
select>
public class TestPlayerMapper {
private PlayerMapper playerMapper= MybatisUtil.getSqlSession().getMapper(PlayerMapper.class);
private TeamMapper teamMapper=MybatisUtil.getSqlSession().getMapper(TeamMapper.class);
@Test
public void test1(){
Player player = playerMapper.queryById(1);
System.out.println(player);
}
@Test
public void test4(){
Player player = playerMapper.queryById3(1);
System.out.println(player);
}
@Test
public void test5(){
Team team = teamMapper.queryById1(1025);
System.out.println(team);
List<Player> playerList = team.getPlayerList1();
System.out.println("该球队的球员个数:"+playerList.size());
playerList.forEach(player -> System.out.println(player));
}
}
动态 SQL 是 MyBatis 的强大特性之一。如果你使用过 JDBC 或其它类似的框架,你应该能理解根据不同条件拼接 SQL 语句有多痛苦,例如拼接时要确保不能忘记添加必要的空格,还要注意去掉列表最后一个列名的逗号。利用动态 SQL,可以彻底摆脱这种痛苦。
使用动态 SQL 并非一件易事,但借助可用于任何 SQL 映射语句中的强大的动态 SQL 语言,MyBatis 显著地提升了这一特性的易用性。
咱们之前学习过 JSTL,所以动态 SQL 元素会让你感觉似曾相识。在 MyBatis 之前的版本中,需要花时间了解大量的元素。借助功能强大的基于 OGNL 的表达式,MyBatis 3 替换了之前的大部分元素,大大精简了元素种类,现在要学习的元素种类比原来的一半还要少。
案例:球队的多条件查询
/*原有的多条件分析:都是通过java中的字符串拼接实现
String sql="select * from team where 1 = 1 ";
// 如果用户输入了名称,就模糊查询
and teamName like '%?%'
// 如果用户输入了日期,按照日期区间查询
and createTime> ? and createTime< ?
//如果输入了地区,按照地区查询
and location =?";*/
if(vo.getName()!=null && !"".equals(vo.getName().trim())){
sql+=" and teamName like '%"+vo.getName().trim()+"%'";
}
if(vo.getBeginTime()!=null ){
sql+=" and getEndTime>"+vo.getBeginTime();
}
if(vo.getBeginTime()!=null ){
sql+=" and createTime<="+vo.getEndTime();
}
if(vo.getLocation()!=null && !"".equals(vo.getLocation().trim())){
sql+=" and location ="+vo.getLocation().trim();
}
自己封装的查询条件类QueryTeamVO.java:
public class QueryTeamVO {
private String name;
private Date beginTime ;
private Date endTime;
private String location;
}
TeamMapper.java接口添加:
List<Team> queryByVO(QueryTeamVO vo);
TeamMapper.xml映射文件添加:
<select id="queryByVO" parameterType="QueryVO" resultMap="baseResultMap" useCache="false">
select * from team
<where>
<if test="name!=null ">
and teamName like concat(concat('%',#{name}),'%')
if>
<if test="beginTime!=null ">
and createTime>=#{beginTime}
if>
<if test="endTime!=null ">
and createTime<=#{endTime}
if>
<if test="location!=null ">
and location=#{location}
if>
where>
select>
测试:
public class TestSQL {
private TeamMapper teamMapper= MybatisUtil.getSqlSession().getMapper(TeamMapper.class);
@Test
public void test1(){
QueryTeamVO vo=new QueryTeamVO();
vo.setName("人");
vo.setEndTime(new Date());
vo.setLocation("加利福尼亚州洛杉矶");
List<Team> teams = teamMapper.queryByVO(vo);
for (Team team : teams) {
System.out.println(team);
}
}
}
TeamMapper.java接口中的方法:
int update(Team team);
TeamMapper.xml映射文件对应的内容:
<update id="update" parameterType="com.kkb.pojo.Team">
update team set teamName=#{teamName},location=#{location},createTime=#{createTime}
where teamId=#{teamId}
update>
测试类中添加测试方法:
@Test
public void test2(){
Team team=new Team();
team.setTeamId(1055);
team.setTeamName("zeyang");
int update = teamMapper.update1(team);
MybatisUtil.getSqlSession().commit();
System.out.println(update);
}
TeamMapper.java接口中添加方法:
int update1(Team team);
TeamMapper.xml映射文件对应的内容:
<update id="update1" parameterType="com.kkb.pojo.Team">
update team
<set>
<if test="teamName!=null">
teamName=#{teamName},
if>
<if test="location!=null">
location=#{location},
if>
<if test="createTime!=null">
createTime=#{createTime},
if>
set>
where teamId=#{teamId}
update>
测试类:
@Test
public void test2(){
Team team=new Team();
team.setTeamId(1055);
team.setTeamName("zeyang");
int update = teamMapper.update1(team);
MybatisUtil.getSqlSession().commit();
System.out.println(update);
}
TeamMapper.java接口中添加方法:
void addList(List<Team> list);
TeamMapper.xml映射文件对应的内容:
<insert id="addList" parameterType="arraylist">
INSERT INTO team (teamName,location) VALUES
<foreach collection="list" item="t" separator=",">
(#{t.teamName},#{t.location})
foreach>
insert>
测试类:
@Test
public void test3(){
List<Team> list=new ArrayList<>();
for(int i=1;i<=3;i++){
Team team=new Team();
team.setTeamName("zeyang"+i);
team.setLocation("bj"+i);
list.add(team);
}
teamMapper.addList(list);
MybatisUtil.getSqlSession().commit();
}
TeamMapper.java接口中添加方法:
void delList(List<Integer> list);
TeamMapper.xml映射文件对应的内容:
<delete id="delList" >
delete from team where teamId in
<foreach collection="list" item="teamId" separator="," open="(" close=")">
#{teamId}
foreach>
delete>
测试类:
@Test
public void test4() {
List<Integer> list = new ArrayList<>();
list.add(1109);
list.add(1110);
list.add(1111);
teamMapper.delList(list);
MybatisUtil.getSqlSession().commit();
}
<dependency>
<groupId>com.github.pagehelpergroupId>
<artifactId>pagehelperartifactId>
<version>5.1.10version>
dependency>
<plugins>
<plugin interceptor="com.github.pagehelper.PageInterceptor">
plugin>
plugins>
@Test
public void test5() {
// PageHelper.startPage 必须紧邻查询语句,而且只对第一条查询语句生效
PageHelper.startPage(2,5);
List<Team> teams = teamMapper.queryAll();//查询语句结尾不能有分号
teams.forEach(team-> System.out.println(team));
PageInfo<Team> info=new PageInfo<>(teams);
System.out.println("分页信息如下:");
System.out.println("当前页:"+info.getPageNum());
System.out.println("总页数:"+info.getPages());
System.out.println("前一页:"+info.getPrePage());
System.out.println("后一页:"+info.getNextPage());
System.out.println("navigatepageNums:"+info.getNavigatepageNums());
for (int num : info.getNavigatepageNums()) {
System.out.println(num);
}
}
PageInfo.java的部分源码:
public class PageInfo<T> extends PageSerializable<T> {
//当前页
private int pageNum;
//每页的数量
private int pageSize;
//当前页的数量
private int size;
//由于startRow和endRow不常用,这里说个具体的用法
//可以在页面中"显示startRow到endRow 共size条数据"
//当前页面第一个元素在数据库中的行号
private int startRow;
//当前页面最后一个元素在数据库中的行号
private int endRow;
//总页数
private int pages;
//前一页
private int prePage;
//下一页
private int nextPage;
//是否为第一页
private boolean isFirstPage;
//是否为最后一页
private boolean isLastPage;
//是否有前一页
private boolean hasPreviousPage;
//是否有下一页
private boolean hasNextPage;
//导航页码数
private int navigatePages;
//所有导航页号
private int[] navigatepageNums;
//导航条上的第一页
private int navigateFirstPage;
//导航条上的最后一页
private int navigateLastPage;
}
缓存是一般的ORM 框架都会提供的功能,目的就是提升查询的效率和减少数据库的压力。将经常查询的数据存在缓存(内存)中,用户查询该数据的时候不需要从磁盘(关系型数据库文件)上查询,而是直接从缓存中查询,提高查询效率,解决高并发问题。
MyBatis 也有一级缓存和二级缓存,并且预留了集成第三方缓存的接口。
Mybatis的缓存结构体系:
在操作数据库时需要构造 sqlSession对象,在对象中有一个(内存区域)数据结构(HashMap)用于存储缓存数据。不同的sqlSession之间的缓存数据区域(HashMap)是互相不影响的。
一级缓存的作用域是同一个SqlSession,在同一个sqlSession中两次执行相同的sql语句,第一次执行完毕会将数据库中查询的数据写到缓存(内存),第二次会从缓存中获取数据将不再从数据库查询,从而提高查询效率。
当一个sqlSession结束后该sqlSession中的一级缓存也就不存在了。
Mybatis默认开启一级缓存,存在内存中(本地缓存)不能被关闭,可以调用clearCache()来清空本地缓存,或者改变缓存的作用域。
工作原理图:
当用户发起第一次查询team=1001的时候,先去缓存中查找是否有team=1001的对象;如果没有,继续向数据中发送查询语句,查询成功之后会将teamId=1001的结果存入缓存中;
当用户发起第2次查询team=1001的时候,先去缓存中查找是否有team=1001的对象,因为第一次查询成功之后已经存储到缓存中,此时可以直接从缓存中获取到该数据,意味着不需要再去向数据库发送查询语句。
如果SqlSession执行了commit(有增删改的操作),此时该SqlSession对应的缓存区域被整个清空,目的避免脏读。
前提:SqlSession未关闭。
测试类:
public class TestCache {
private SqlSession sqlSession= MybatisUtil.getSqlSession();
//测试一级缓存:自动开启,sqlSession级别的缓存
@Test
public void test1() {
Team t1=sqlSession.selectOne("com.kkb.mapper.TeamMapper.queryById",1001);//第一次查询,先查缓存,此时缓存中没有,继续向数据库发送查询语句
System.out.println(t1);//查询完毕之后数据被自动存入缓存区域
Team t2=sqlSession.selectOne("com.kkb.mapper.TeamMapper.queryById",1001);//第二次查询,因为缓存中已经有了该数据,可以直接获取,不需要发送查询语句
System.out.println(t2);
MybatisUtil.closeSqlSession();//关闭连接,缓存清空
sqlSession=MybatisUtil.getSqlSession();//再次获取连接,此时缓存为空
Team t3=sqlSession.selectOne("com.kkb.mapper.TeamMapper.queryById",1001);//新连接下第一次查询,肯定发送查询语句
System.out.println(t3);//查询完毕之后数据被自动存入缓存区域
int num=sqlSession.delete("com.kkb.mapper.TeamMapper.del",10000);
sqlSession.commit();//提交之后缓存被整个清空
System.out.println("删除结果:"+num);
Team t4=sqlSession.selectOne("com.kkb.mapper.TeamMapper.queryById",1001);//第二次查询,因为缓存已经被上一次的提交清空了,所以还是需要发送查询语句
System.out.println(t4);
sqlSession.close();
}
}
多个SqlSession去操作同一个Mapper的sql语句,多个SqlSession去操作数据库得到数据会存在二级缓存区域,多个SqlSession可以共用二级缓存,二级缓存是跨SqlSession的。
二级缓存是多个SqlSession共享的,其作用域是mapper的同一个namespace。
不同的sqlSession两次执行相同namespace下的sql语句参数相同即最终执行相同的sql语句,第一次执行完毕会将数据库中查询的数据写到缓存(内存),第二次会从缓存中获取数据将不再从数据库查询,从而提高查询效率。
Mybatis默认没有开启二级缓存,需要在setting全局参数中配置开启二级缓存。
如果缓存中有数据就不用从数据库中获取,大大提高系统性能。
二级缓存是mapper范围级别的,默认不启用。
<setting name="cacheEnabled" value="true"/>
/*
测试2级缓存:前提3点:全局配置文件开启了二级缓存;TeamMapper.xml配置了缓存;Team实体类实现了序列化接口
*/
@Test
public void test2() {
SqlSession sqlSession1 = MybatisUtil.getSqlSession();
Team t1 = sqlSession1.selectOne("com.kkb.mapper.TeamMapper.queryById", 1001);//先查缓存,没有,先数据库,查询完毕写入二级缓存
System.out.println(t1);
MybatisUtil.closeSqlSession();//关闭连接,一级缓存清空,二级缓存存在
SqlSession sqlSession2 = MybatisUtil.getSqlSession();
Team t2 = sqlSession2.selectOne("com.kkb.mapper.TeamMapper.queryById", 1001);//先查缓存,有,直接获取,不需要查询数据库
System.out.println(t2);
MybatisUtil.closeSqlSession();//关闭连接,一级缓存清空,二级缓存存在
SqlSession sqlSession3 = MybatisUtil.getSqlSession();
int num = sqlSession3.delete("com.kkb.mapper.TeamMapper.del", 10000);//删除成功
System.out.println("删除的结果:" + num);
sqlSession3.commit();//提交之后清空二级缓存
MybatisUtil.closeSqlSession();//关闭连接,缓存清空
SqlSession sqlSession4 = MybatisUtil.getSqlSession();
Team t3 = sqlSession4.selectOne("com.kkb.mapper.TeamMapper.queryById", 1001);先查缓存,曾经有,但是上一个提交已经清空了缓存,所以只能去数据库中查询,查询完毕写入二级缓存
System.out.println(t3);
MybatisUtil.closeSqlSession();//关闭连接,缓存清空
}
对于变化比较频繁的SQL,可以禁用二级缓存。
在开始了二级缓存的XML中对应的statement中设置useCache=false禁用当前Select语句的二级缓存,意味着该SQL语句每次只需都去查询数据库,不会查询缓存。
useCache默认值是true。对于一些很重要的数据尽不放在二级缓存中。
<cache>
<property name="eviction" value="LRU"/>
<property name="flushInterval" value="60000"/>
<property name="size" value="1024"/>
<property name="readOnly" value="true"/>
cache>
源码:
如果想在命名空间中共享相同的缓存配置和实例,可以使用cache-ref 元素来引用另外一个缓存。
<cache-ref namespace="com.kkb.mapper.TeamMapper" />
//引用TeamMapper命名空间中的cache。
在pom.xml文件中的中中添加如下插件配置
<plugin>
<groupId>org.mybatis.generatorgroupId>
<artifactId>mybatis-generator-maven-pluginartifactId>
<version>1.3.5version>
<configuration>
<configurationFile>src/main/resources/generatorConfig.xmlconfigurationFile>
<overwrite>trueoverwrite>
configuration>
<dependencies>
<dependency>
<groupId>org.mybatis.generatorgroupId>
<artifactId>mybatis-generator-coreartifactId>
<version>1.3.5version>
dependency>
dependencies>
plugin>
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>
<classPathEntry
location="D:\repository\mysql\mysql-connector-java\8.0.23\mysql-connector-java-8.0.23.jar" />
<context id="MyBatis" targetRuntime="MyBatis3">
<commentGenerator>
<property name="suppressAllComments" value="true" />
commentGenerator>
<jdbcConnection driverClass="com.mysql.cj.jdbc.Driver"
connectionURL="jdbc:mysql://127.0.0.1:3306/mybatis?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone=GMT"
userId="root"
password="123456">
jdbcConnection>
<javaTypeResolver>
<property name="forceBigDecimals" value="false" />
javaTypeResolver>
<javaModelGenerator targetPackage="org.xzk.pojo"
targetProject="src\main\java">
<property name="trimStrings" value="true" />
javaModelGenerator>
<sqlMapGenerator targetPackage="org.xzk.mapper"
targetProject="src\main\resources">
sqlMapGenerator>
<javaClientGenerator type="XMLMAPPER"
targetPackage="org.xzk.mapper"
targetProject="src\main\java">
javaClientGenerator>
<table tableName="Team">
<property name="useActualColumnNames" value="true"/>
table>
<table tableName="Player">
<property name="useActualColumnNames" value="true"/>
table>
<table tableName="gameRecord">
<property name="useActualColumnNames" value="true"/>
table>
context>
generatorConfiguration>
注意只能运行一次,运行完毕显示BUILD SUCCESS即为成功。
public class TestGenerator {
private TeamMapper mapper= MybatisUtil.getSqlSession().getMapper(TeamMapper.class);
@Test
public void test1(){
Team team = mapper.selectByPrimaryKey(1001);
System.out.println(team);
}
@Test
public void test2(){
Team team=new Team();
team.setTeamName("lina-test");
team.setLocation("bj");
int i = mapper.insert(team);
MybatisUtil.getSqlSession().commit();
System.out.println(i);
}
@Test
public void test3(){
//可以理解为为多条件、排序等服务的类
TeamExample example=new TeamExample();
//理解为盛放条件的容器
TeamExample.Criteria criteria = example.createCriteria();
//向容器中添加条件
criteria.andTeamNameLike("人");
criteria.andTeamIdBetween(1001,1100);
//排序
example.setOrderByClause("teamName desc");
List<Team> teams = mapper.selectByExample(example);
for (Team team : teams) {
System.out.println(team);
}
}
}