Mybatis
MyBatis介绍
1、MyBatis的介绍
MyBatis最初是Apache的一个开源项目iBatis,2010年6月这个项目由Apache Software Foundation迁移到了Google Code。随着开发团队转投Google Code旗下,iBatis3.x正式更名为MyBatis。代码于2013年11月迁移到了Github。
iBatis一词来源于Internet和abatis的组合,是一个基于Java的持久层框架。iBatis提供的持久层框架包括SQL Maps和Data Access Object(DAO)。
Mybatis通过xml或注解的方式将要执行的各种statement(statement、preparedStatemnt)配置起来,并通过java对象和statement中的sql进行映射生成最终执行的sql语句,最后由mybatis框架执行sql并将结果映射成java对象并返回。
总之,Mybatis对JDBC访问数据库的过程进行了封装,简化了JDBC代码,解决JDBC将结果集封装为Java对象的麻烦。
下图是MyBatis架构图:

(1)mybatis-config.xml是Mybatis的核心配置文件,通过其中的配置可以生成SqlSessionFactory,也就是SqlSession工厂
(2)基于SqlSessionFactory可以生成SqlSession对象
(3)SqlSession是一个既可以发送SQL去执行,并返回结果,类似于JDBC中的Connection对象,也是Mybatis中至关重要的一个对象。
(4)Executor是SqlSession底层的对象,用于执行SQL语句
(5)MapperStatement对象也是SqlSession底层的对象,用于接收输入映射(SQL语句中的参数),以及做输出映射(即将SQL查询的结果映射成相应的结果)
2、MyBatis特性
MyBatis是支持定制化SQL、存储过程以及高级映射的优秀的持久层框架
MyBatis避免了几乎所有的JDBC代码和手动设置以及获取结果集的代码
MyBatis可以使用简单的XML或注解用于配置和原始映射,将接口和Java的POJO(Plain OldJava Objects,普通的Java对象)映射成数据库中的记录
MyBatis是一个半自动的ORM(ObjectRelation Mapping)框架
3、MyBatis下载
MyBatis下载地址 https://github.com/mybatis/mybatis-3


4、MyBatis和其他持久层技术的对比
JDBC
SQL夹杂在Java代码中,耦合度高,导致硬编码内伤
维护不易且实际开发需求中SQL有变化,频繁修改的情况多见
代码冗长、开发效率低
Hibernate和JPA
操作简便,开发效率高
程序中的长难复杂SQL需要绕过框架
内部自动生产的SQL,不容易做特殊优化
基于全映射的全自动框架,大量字段的POJO进行部分映射时比较困难
反射操作太多,导致数据库性能下降
MyBatis
轻量级,性能出色
SQL和Java编码分开,功能边界清晰。Java代码专注业务。SQL语句专注数据
开发效率稍逊于Hibernate,但是完全能够接受
MyBatis入门
1、准备数据

2、创建Maven的java工程

3、添加依赖
在pom.xml文件中引入相关依赖包即可
<dependencies>
<!-- junit单元测试 -->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
</dependency>
<!-- mysql驱动 -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.32</version>
</dependency>
<!-- mybatis -->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.5.11</version>
</dependency>
</dependencies>
4、创建MyBatis的核心配置文件
核心配置文件主要用于配置连接数据库的环境以及Mybatis的全局配置信息,将来SSM整合后,这个配置文件可以省略。
<?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN" "" target="_blank">https://mybatis.org/dtd/mybatis-3-config.dtd" target="_blank">"> <configuration> <environments default="development"> <environment id="development"> <transactionManager type="JDBC" /> <dataSource type="POOLED"> <property name="driver" value="com.mysql.jdbc.Driver" /> <property name="url" value="jdbc:mysql://localhost:3306/shenmu?characterEncoding=utf-8" /> <property name="username" value="root" /> <property name="password" value="root" /> </dataSource> </environment> </environments> <mappers> <!-- 引入mapper文件 --> <mapper resource="mapper/UserMapper.xml" /> </mappers> </configuration> |
了解映射关系:
数据库中一张表对应Java中的一个class(tb_useràUser),一条表记录对应一个对象
一张表对应一个Mapper文件,如对tb_user表所做的CRUD操作,都写在UserMapper中
一个Mapper文件对应一个Dao层接口的,一条sql就对应接口中的一个方法
使用Mybatis需要关注:
SqlSession对象:是mybatis提供给我们用来和数据库进行交互的对象
mapper文件:写sql语句的,一般一张表对应一个mapper文件
mapper接口:一个接口对应一个mapper文件,接口中的一个方法就对应mapper文件中的一条sql

5、创建实体类
/** * 此实体类User和表tb_user表对应 * 到时候tb_user中的一条表记录就可以用一个User对象封装 * 因此此类中属性和表中字段一一对应 */ public class User { private Integer id;//用户ID private String username;//用户名 private String password;//用户密码 private Double account;//账户余额 //无参,全参,get+set,toString |

6、创建mapper接口
public interface UserDao { //新增员工数据 intinsertUser(); } |
一个接口对应一个mapper文件,接口中的一个方法对应mapper文件中的一条sql
7、创建mapper映射文件(记得要在核心配置文件中配置)
相关概念ORM(Object RelationShipMapping) 对象关系映射。
对象:Java的实体类对象
关系: 关系型数据库
映射: 二者之间的对应关系
Java中的类 -> 数据库中的表
类的属性 -> 表中字段(列)
对象 -> 表记录(行)
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "" target="_blank">http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<!-- MyBatis支持面向接口编程的,我们的一个接口就对应这里的一个mapper文件 接口中的方法就对应mapper文件中的一条sql,他们是如何对应起来的? public interface UserDao { //新增员工数据 int insertUser(); } 如上,mapper文件namespace的值=接口的全限定类名 mapper文件中的一条sql语句的id属性值=接口中的一个方法名 --> <mapper namespace="cn.niit.dao.UserDao"> <insert id="insertUser"> insert into tb_user values(null,'赵六','123',5000) </insert> </mapper> |
8、测试
public class TestMyBatis { @Test public void testMyBatis() throws IOException { //1、加载核心配置文件 InputStream in = Resources.getResourceAsStream("mybatis-config.xml"); //2、创建SqlSessionFactoryBuilder对象 SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder(); //3、创建SqlSessionFactory SqlSessionFactory factory = builder.build(in); //4、创建SqlSession SqlSession sqlSession = factory.openSession(); //5、获取UserDao接口的实现类 UserDao userDao = sqlSession.getMapper(UserDao.class); //6、调用对应的方法,执行方法对应的sql,获取结果 int rows = userDao.insertUser(); System.out.println("受影响的行数:"+rows); } } |
SqlSession:代表Java程序和数据库之间的会话(HttpSession是Java程序和浏览器间的会话)
sqlSessionFactory:是生产SqlSession的工厂
工厂模式:如果创建某一个对象,使用的过程基本固定,那么就可以把创建这个对象相关的代码封装到一个工厂类中,以后就可以都使用这个工厂类来生产我们需要的这个对象了。
测试:发现控制台有打印但是数据没有新增成功
这是因为我们配置的事务是JDBC,则需要手动提交事务
修改代码:
public class TestMyBatis { @Test public void testMyBatis() throws IOException { //1、加载核心配置文件 InputStream in = Resources.getResourceAsStream("mybatis-config.xml"); //2、创建SqlSessionFactoryBuilder对象 SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder(); //3、创建SqlSessionFactory SqlSessionFactory factory = builder.build(in); //4、创建SqlSession SqlSession sqlSession = factory.openSession(); //5、获取UserDao接口的实现类 UserDao userDao = sqlSession.getMapper(UserDao.class); //6、调用对应的方法,执行方法对应的sql,获取结果 int rows = userDao.insertUser(); //提交事务 sqlSession.commit(); System.out.println("受影响的行数:"+rows); } } |

9、设置自动提交事务
SqlSession默认不自动提交事务,可以设置自动提交事务
public class TestMyBatis { @Test public voidtestMyBatis() throws IOException { //1、加载核心配置文件 InputStream in = Resources.getResourceAsStream("mybatis-config.xml"); //2、创建SqlSessionFactoryBuilder对象 SqlSessionFactoryBuilderbuilder = new SqlSessionFactoryBuilder(); //3、创建SqlSessionFactory SqlSessionFactory factory = builder.build(in); //4、创建SqlSession SqlSession sqlSession = factory.openSession(true); //5、获取UserDao接口的实现类 UserDao userDao = sqlSession.getMapper(UserDao.class); //6、调用对应的方法,执行方法对应的sql,获取结果 int rows = userDao.insertUser(); //提交事务 //sqlSession.commit(); System.out.println("受影响的行数:"+rows); } } |

10、加入log4j日志功能
导入log4J的jar包
<!-- 整合log4j -->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
<version>1.6.4</version>
</dependency>
在项目中加入log4j的配置文件,用于打印日志信息,便于开发调试。
在src(或相似的目录)下创建log4j.properties如下:
# Globallogging configuration
log4j.rootLogger=DEBUG, stdout
# Consoleoutput...
log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern=%5p [%t] - %m%n
mybatis默认使用log4j作为输出日志信息。
只要将该文件放在指定的位置,log4j工具会自动到指定位置加载上述文件,读取文件中的配置信息并使用!
日志级别:
FATAL(致命)>ERROR(错误)>WARN(警告)>INFO(信息)>DEBUG(调试)
日志级别越低,打印的内容越详细

使用MyBatis实现数据的CRUD
配置好MyBatis的环境后,对于同一张表的操作,只需要在接口中添加方法,到mapper文件中写对应的sql,通过sqlSession获取接口的实现类对象并调用对应的方法即可。
修改id为1的用户余额为2000
添加方法
//修改id为1的用户余额为2000 int updateUserById(); |
写sql语句
<!-- 修改id为1的用户余额为2000 int updateUserById(); --> <update id="updateUserById"> update tb_user set account = 2000 where id = 1 </update> |
测试
@Test public void testMyBatis() throws IOException { //1、加载核心配置文件 InputStream in = Resources.getResourceAsStream("mybatis-config.xml"); //2、创建SqlSessionFactoryBuilder对象 SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder(); //3、创建SqlSessionFactory SqlSessionFactory factory = builder.build(in); //4、创建SqlSession SqlSession sqlSession = factory.openSession(true); //5、获取UserDao接口的实现类 UserDao userDao = sqlSession.getMapper(UserDao.class); //6、调用对应的方法,执行方法对应的sql,获取结果 //int rows = userDao.insertUser(); int rows = userDao.updateUserById(); //提交事务 //sqlSession.commit(); System.out.println("受影响的行数:"+rows); } |
练习:删除id为3的用户信息
查询id为1的用户信息
添加方法
//查询id为1的员工数据 User seleteUserById(); |
写sql
<!-- //查询id为1的用户数据User seleteUserById(); 查询功能的标签必须设置resultType或resultMap resultType: 设置默认的映射关系(字段和属性名一致,用resultType自动映射即可) resultMap:设置自定义的映射关系 --> <select id="seleteUserById" resultType="cn.niit.entity.User"> select * from tb_user where id = 1; </select> |
测试
@Test public void testMyBatis() throws IOException { //1、加载核心配置文件 InputStream in = Resources.getResourceAsStream("mybatis-config.xml"); //2、创建SqlSessionFactoryBuilder对象 SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder(); //3、创建SqlSessionFactory SqlSessionFactory factory = builder.build(in); //4、创建SqlSession SqlSession sqlSession = factory.openSession(true); //5、获取UserDao接口的实现类 UserDao userDao = sqlSession.getMapper(UserDao.class); //6、调用对应的方法,执行方法对应的sql,获取结果 //int rows = userDao.insertUser(); //int rows = userDao.updateUser(); //int rows = userDao.deleteUser(); User user = userDao.seleteUserById(); System.out.println(user); //提交事务 //sqlSession.commit(); //System.out.println("受影响的行数:"+rows); } |
查询所有用户数据
添加方法
//查询所有用户数据 List<User> selectAllUser(); |
写sql
<!-- //查询所有用户数据 List<User> selectAllUser(); --> <select id="selectAllUser" resultType="cn.niit.entity.User"> select * from tb_user </select> |
测试
@Test public void testMyBatis() throws IOException { //1、加载核心配置文件 InputStream in = Resources.getResourceAsStream("mybatis-config.xml"); //2、创建SqlSessionFactoryBuilder对象 SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder(); //3、创建SqlSessionFactory SqlSessionFactory factory = builder.build(in); //4、创建SqlSession SqlSession sqlSession = factory.openSession(true); //5、获取UserDao接口的实现类 UserDao userDao = sqlSession.getMapper(UserDao.class); //6、调用对应的方法,执行方法对应的sql,获取结果 List<User> list = userDao.selectAllUser(); for (User user : list) { System.out.println(user); } } |
MyBatis核心配置文件
1、配置文件详解
<?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN" "" target="_blank">https://mybatis.org/dtd/mybatis-3-config.dtd"> <configuration> <!-- 一个environments中可以配置多个environment default属性值设置默认使用的环境id 一个environment对应一个数据库环境 id属性值为当前环境的唯一标识,不能重复 --> <environments default="development"> <environment id="development"> <!-- transactionManager:设置事务管理方式 type="JDBC|MANAGED" JDBC表示当前环境中,使用JDBC中原生的事务管理方式,事务的提交或回滚需要手动处理 MANAGED:被管理,比如spring --> <transactionManager type="JDBC" /> <dataSource type="POOLED"> <!-- dataSource:配置数据源 type:设置数据源的类型 type="POOLED|UNPOOLED|JNDI" POOLED:表示使用连接池 UNPOOLED:表示不使用连接池 JNDI:表示使用上下文中的数据源(已过时) --> <property name="driver" value="com.mysql.jdbc.Driver" /> <property name="url" value="jdbc:mysql://localhost:3306/shenmu?characterEncoding=utf-8" /> <property name="username" value="root" /> <property name="password" value="root" /> </dataSource> </environment> </environments> <!-- 一个mappers中可以配置多个mapper --> <mappers> <!-- 引入mapper文件 --> <mapper resource="mapper/UserMapper.xml" /> </mappers> </configuration> |
2、jdbc.properties
准备jdbc.properties

jdbc.driver=com.mysql.jdbc.Driver jdbc.url=jdbc:mysql://localhost:3306/shenmu?characterEncoding=utf-8 jdbc.username=root jdbc.password=root |
在核心配置文件中引入properties文件
<?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN" "" target="_blank">https://mybatis.org/dtd/mybatis-3-config.dtd"> <configuration> <properties resource="jdbc.properties"/> <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文件 --> <mapper resource="mapper/UserMapper.xml"/> </mappers> </configuration> |
3、设置别名

<!-- 设置别名 --> <typeAliases> <!-- typeAlias用来设置某个类型的别名(别名不区分大小写) type:需要设置别名的类型 alias:设置的别名,若不设置则默认为类名且不区分大小写 --> <!-- <typeAlias type="cn.niit.entity.User"/> --> <!-- 以包为单位,为包下所有类型设置默认的别名即类名且不区分大小写 --> <package name="cn.niit.entity"/> </typeAliases> |
测试
<!-- //查询所有用户数据 List<User> selectAllUser(); --> <select id="selectAllUser" resultType="user"> select * from tb_user </select> |
MyBatis获取参数值的两种方式
准备一个工具类,封装重复代码
public class SqlSessionUtil { public staticSqlSession getSqlSession(String path) { SqlSession sqlSession = null; try { //读取mybatis核心配置文件 InputStream in = Resources.getResourceAsStream(path); //构建SqlSessionFactory工厂 SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(in); //创建SqlSession对象 sqlSession = factory.openSession(true); } catch (IOException e) { e.printStackTrace(); } return sqlSession; } } |
MyBatis获取参数值的两种方式:${}和 #{}
${} 的本质就是字符串拼接,#{}的本质就是占位符赋值
${} 使用字符串拼接的方式拼接sql,若参数值为字符串或日期类型,则需要手动加单引号
#{} 使用占位符赋值的方式拼接sql,此时参数值为字符串或日期类型,会自动添加单引号
能用#{}就不用${},有时会有需要用${}的情况
需要注意的是${}取值,必须将值放到map中,且一般用的是#{},而${}多用于为不带引号的字符串进行占位。
1、单个字面量类型的参数
若mapper接口中的方法参数为单个的字面量类型
此时可以使用${}和#{}以任意名称获取参数的值,注意${}需要手动加单引号。
也可以为参数加上@Param注解,则mybatis会将此参数放到map集合中,以@Param注解的值、param1,param2...为key,值为参数值。这样我们需要通过key去获取对应的参数值了。
练习:查询指定id的用户信息
添加方法
public interface UserDao { //查询指定id的用户信息 User selectUserById(@Param("id") Integer id); } |
写sql
<!-- 查询指定id的用户信息 User selectUserById(@Param("id") Integer id); --> <select id="selectUserById" resultType="user"> select * from tb_user where id = #{id} </select> |
测试:
@Test public voidtestSelectById() { //通过工具类获取sqlSession SqlSession sqlSession = SqlSessionUtil.getSqlSession("mybatis-config.xml"); //通过sqlSession获取UserDao接口的实现类对象 UserDao userDao = sqlSession.getMapper(UserDao.class); //调用方法,执行对应的sql语句 User user = userDao.selectUserById(1); System.out.println(user); } |
2、多个字面量类型的参数
若mapper接口中的方法参数为多个时,此时MyBatis会自动将这些参数放在一个map集合中,默认以arg0,arg1…为key,以参数为值value。以param1、param2…为为key,以参数为值value;因此只需要通过${}和#{}访问map集合中的key就可以获取对应的value值。注意${}需要手动添加单引号。
也可以为每个参数加上@Param注解,则mybatis会将这些参数放到一个map集合中,以两种方式存储
以@Param的值为key,参数值为值
以param1,param2…为key,参数值为值
因此只需要通过#{}/${}通过key取参数值即可,注意${}需要手动添加单引号
练习:验证用户的用户名和密码
准备接口中的方法
public interface UserDao { //查询指定id的用户信息 User selectUserById(@Param("id") Integer id); //验证用户的用户名和密码 User selectUserByUserAndPasswd(@Param("username") String username,@Param("passwd")String passwd); } |
写sql
<!-- 验证用户的用户名和密码 User selectUserByUserAndPasswd(String username,String passwd); 当传多个值时,mybatis会自动将这些值都放到map中, key为arg0,arg1..或者param1,param2... User selectUserByUserAndPasswd(@Param("username") String username,@Param("passwd")String passwd); 或者可以选择加上@Param注解来指定map中的key --> <select id="selectUserByUserAndPasswd" resultType="user"> <!-- select * from tb_user where username = #{arg0} and password = #{param2} --> select * from tb_user where username = #{username} and password = #{passwd} </select> |
测试
@Test public void testSelectUserByUsernameAndPasswd() { //通过工具类获取sqlSession SqlSession sqlSession = SqlSessionUtil.getSqlSession("mybatis-config.xml"); //通过sqlSession获取UserDao接口的实现类对象 UserDao userDao = sqlSession.getMapper(UserDao.class); //调用方法,执行对应的sql语句 User user = userDao.selectUserByUserAndPasswd("张三", "123"); System.out.println(user); } |
3、多个参数用Map封装传值
当参数有多个时,可以手动将这些参数放到一个map中存储,只需要通过#{}和${}以key的方式获取对应的参数值即可,注意${}需要手动添加单引号。
练习:新增用户
接口中添加方法
public interface UserDao { //查询指定id的用户信息 User selectUserById(@Param("id") Integer id); //验证用户的用户名和密码 User selectUserByUserAndPasswd(@Param("username") String username, @Param("passwd")String passwd); //新增用户 int insertUser(Map<String, Object> map); } |
写sql
<!-- 新增用户 int insertUser(Map<String, Object> map); Map<String, Object> map = new HashMap<>(); map.put("username", "张飞"); map.put("passwd", "zf"); map.put("account", 1000); int rows = userDao.insertUser(map); --> <insert id="insertUser"> insert into tb_user values(null,#{username},#{passwd},#{account}) </insert> |
测试
@Test public voidtestInsertUser() { //通过工具类获取sqlSession SqlSession sqlSession = SqlSessionUtil.getSqlSession("mybatis-config.xml"); //通过sqlSession获取UserDao接口的实现类对象 UserDao userDao = sqlSession.getMapper(UserDao.class); //调用方法,执行对应的sql语句 Map<String, Object> map = new HashMap<>(); map.put("username", "张飞"); map.put("passwd", "zf"); map.put("account", 1000); int rows = userDao.insertUser(map); System.out.println("受影响的行数:"+rows); } |
4、多个参数用实体类对象封装传值
修改用户信息:
当多个参数使用实体类对象封装传值,只需要使用#{}或者${}以属性的方式访问对应的属性值即可,注意${}需要手动添加单引号
接口中添加方法
//修改用户信息(用实体类传参) int updateUserById(User user); |
写sql
<!-- //修改用户信息(用实体类传参) int updateUserById(User user); 当参数值封装在java对象中传过来时,#{}以属性的方式访问对应的属性值 --> <update id="updateUserById"> update tb_user set username=#{username},password=#{password},account=#{account} where id = #{id} </update> |
测试
@Test public voidtestUpdateUser() { //通过工具类获取sqlSession SqlSession sqlSession = SqlSessionUtil.getSqlSession("mybatis-config.xml"); //通过sqlSession获取UserDao接口的实现类对象 UserDao userDao = sqlSession.getMapper(UserDao.class); //调用方法,执行对应的sql语句 User user = new User(6, "孙策", "sc", 10000.0); userDao.updateUserById(user); } |
MyBatis的各种查询
1、查询结果为单行单列的数据
若查询结果是单行单列的数据,则按对应类型接收即可,可以用别名
MyBatis中设置了默认的类型别名:
java.lang.Integer à int,integer(别名不区分大小写)
int -> _int,_integer
Map -> map
String -> string
方法:
//查询总记录数 Integer selectCount(); |
sql:
<!-- 查询总记录数 Integer selectCount(); --> <select id="selectCount" resultType="int"> select count(*) from tb_user; </select> |
测试:
@Test public voidtestSelectCount() { //通过工具类获取sqlSession SqlSession sqlSession = SqlSessionUtil.getSqlSession("mybatis-config.xml"); //通过sqlSession获取UserDao接口的实现类对象 UserDao userDao = sqlSession.getMapper(UserDao.class); //调用方法,执行对应的sql语句 Integer count = userDao.selectCount(); System.out.println("总记录数为:"+count); } |
2、查询结果为一条表记录
若查询结果只有一条数据
可以用实体类对象接收
可以用map接收
并且他们可以放到list集合中
结果用实体类接收(略)
结果可以用map集合接收
方法
//查询指定id的用户信息 Map<String, Object> selectUserById(@Param("id") Integer id); |
sql:
<select id="selectUserById" resultType="map"> select * from tb_user where id = #{id} </select> |
测试
@Test public void testSelectById() { //通过工具类获取sqlSession SqlSession sqlSession = SqlSessionUtil.getSqlSession("mybatis-config.xml"); //通过sqlSession获取UserDao接口的实现类对象 UserDao userDao = sqlSession.getMapper(UserDao.class); //调用方法,执行对应的sql语句 Map<String, Object> user = userDao.selectUserById(1); System.out.println(user); } |

结果可以放到list集合中
方法
//查询指定id的用户信息 List<User> selectUserById(@Param("id") Integer id); |
sql
<!-- 查询指定id的用户信息 User selectUserById(@Param("id") Integer id); --> <select id="selectUserById" resultType="user"> select * from tb_user where id = #{id} </select> |
测试
@Test public voidtestSelectById() { //通过工具类获取sqlSession SqlSession sqlSession = SqlSessionUtil.getSqlSession("mybatis-config.xml"); //通过sqlSession获取UserDao接口的实现类对象 UserDao userDao = sqlSession.getMapper(UserDao.class); //调用方法,执行对应的sql语句 List<User> user = userDao.selectUserById(1); System.out.println(user); } |

3、查询结果为多条记录,用list集合接收
若查询结果有多条表记录
可以用list集合接收
可以用map接收
注意:一定不能通过实体类对象接收,此时会抛异常
结果可以用list集合接收
结果可以用List集合接收,list中可以是实体类对象,也可以是map
方法:
//查询所有用户数据 List<Map> selectAllUser(); |
sql:
<!-- 查询所有用户数据 selectAllUser --> <select id="selectAllUser" resultType="map"> select * from tb_user; </select> |
测试:
@Test public voidtestSelectAllUser() { //通过工具类获取sqlSession SqlSession sqlSession = SqlSessionUtil.getSqlSession("mybatis-config.xml"); //通过sqlSession获取UserDao接口的实现类对象 UserDao userDao = sqlSession.getMapper(UserDao.class); //调用方法,执行对应的sql语句 List<Map> user = userDao.selectAllUser(); System.out.println(user); } |

结果一定不能用一个实体类对象接收

结果可以用一个map接收
方法:
/* * 加上@MapKey注解,此时就可以将每条数据转换的map集合作为值, * 以某个字段的值作为键,放在同一个map集合中 * 注意这个字段不能重复,一般是主键 */ @MapKey("id") Map selectAllUser(); |
sql
<!-- 查询所有用户数据 selectAllUser --> <select id="selectAllUser" resultType="map"> select * from tb_user; </select> |
测试:
@Test public void testSelectAllUser() { //通过工具类获取sqlSession SqlSession sqlSession = SqlSessionUtil.getSqlSession("mybatis-config.xml"); //通过sqlSession获取UserDao接口的实现类对象 UserDao userDao = sqlSession.getMapper(UserDao.class); //调用方法,执行对应的sql语句 Mapuser = userDao.selectAllUser(); System.out.println(user); } |

特殊sql执行
1、模糊查询
需求:根据用户名模糊查询

方法:
//根据用户名模糊查询 List<User> selectByUserName(@Param("username") String username); |
sql
<!-- 根据用户名模糊查询 List<User> selectByUserName (@Param("username") String username); --> <select id="selectByUserName" resultType="user"> <!-- select * from tb_user where username like '%${username}%' --> <!-- select * from tb_user where username like concat('%',#{username},'%') --> select * from tb_user where username like #{username} </select> |
测试:
@Test public void testSelectById() { //通过工具类获取sqlSession SqlSession sqlSession = SqlSessionUtil.getSqlSession("mybatis-config.xml"); //通过sqlSession获取UserDao接口的实现类对象 UserDao userDao = sqlSession.getMapper(UserDao.class); //调用方法,执行对应的sql语句 String username = "张"; List<User> list = userDao.selectByUserName("%"+username+"%"); for (User user : list) { System.out.println(user); } } |
2、批量删除
方法:
//根据id批量删除数据 参数如1,2,3 int deletBatch(@Param("ids") String ids); |
sql:
<!-- 根据id批量删除数据 参数如1,2,3 int deletBatch(@Param("ids") String ids); --> <delete id="deletBatch"> delete from tb_user where id in(${ids}) </delete> |
测试:
@Test public voidtestDeleteBatch() { //通过工具类获取sqlSession SqlSession sqlSession = SqlSessionUtil.getSqlSession("mybatis-config.xml"); //通过sqlSession获取UserDao接口的实现类对象 UserDao userDao = sqlSession.getMapper(UserDao.class); //调用方法,执行对应的sql语句 int rows = userDao.deletBatch("1,3,5"); System.out.println("受影响的行数:"+rows); } |
3、动态设置列名
方法:
//动态设置列 List<User> selectByCols(@Param("cols") String cols); |
sql:
<select id="selectByCols" resultType="user"> select ${cols} from tb_user; </select> |
测试:
@Test public voidtestSelectByCols() { //通过工具类获取sqlSession SqlSession sqlSession = SqlSessionUtil.getSqlSession("mybatis-config.xml"); //通过sqlSession获取UserDao接口的实现类对象 UserDao userDao = sqlSession.getMapper(UserDao.class); //调用方法,执行对应的sql语句 List<User> list = userDao.selectByCols("id,username"); System.out.println(list); } |
4、添加功能获取自增的主键
当添加用户信息是,由于主键自增,我们新增时并不知道新增的数据的主键值时多少,如何在新增时获取这个主键呢?
方法:
//添加用户信息 int insertUser(User user); |
sql:
<!-- 添加用户信息int insertUser(@Param("user") User user); useGeneratedKeys:设置当前标签中sql使用了自增的主键 keyProperty:将自增的主键的值传输到参数中的某个属性中,这里用id属性接收 --> <insert id="insertUser" useGeneratedKeys="true" keyProperty="id"> insert into tb_user values(null,#{username},#{password},#{account}) </insert> |
ResultMap自定义映射
准备员工表和部门表,两张表为多对一的关系


1、解决表中字段和实体类中属性不一致
需求:查询所有的员工数据
准备环境:

核心配置文件:
<mappers> <!-- 引入mapper文件 --> <mapper resource="mapper/EmpMapper.xml"/> <mapper resource="mapper/DeptMapper.xml"/> </mappers> |
Emp.java
public class Emp { private Integer eid; //eid private String empName;//emp_name private Integer age; //age private String job;//job private Double salary; //salary //无参+全参+get+set |
接口中添加方法
public interface EmpDao { //查询所有员工 List<Emp> selectAll(); } |
mapper文件中添加对应sql
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "" target="_blank">http://mybatis.org/dtd/mybatis-3-mapper.dtd"> <mapper namespace="cn.niit.dao.EmpDao"> <!-- 查询所有员工 List<Emp> selectAll(); --> <select id="selectAll" resultType="emp"> select * from emp </select> </mapper> |
测试:
@Test public void testSelectAll() { SqlSession sqlSession = SqlSessionUtil.getSqlSession("mybatis-config.xml"); //获取EmpDao接口的实现类对象 EmpDao empDao = sqlSession.getMapper(EmpDao.class); //调用对应方法,执行sql List<Emp> list = empDao.selectAll(); System.out.println(list); } |
结果:

通过起别名解决
<!-- 查询所有员工 List<Emp> selectAll(); --> <select id="selectAll" resultType="emp"> select eid,emp_name empName,age,job,salary from emp </select> |
测试结果:

设置驼峰规则
在核心配置文件中配置驼峰映射
<!-- MyBatis全局配置 --> <settings> <!-- 可以自动将字段中的_映射为驼峰规则如emp_name:empName --> <setting name="mapUnderscoreToCamelCase" value="true"/> </settings> |
测试:
<!-- 查询所有员工 List<Emp> selectAll(); --> <select id="selectAll" resultType="emp"> select * from emp </select> |
通过ResultMap自定义映射规则
<!-- 查询所有员工 List<Emp> selectAll(); --> <!-- 通过resultMap标签自定义映射规则 id属性设置的是此resultMap的唯一标识 type:设置映射的类型 --> <resultMap type="emp" id="empResultMap"> <!-- 通过id子标签设置主键的映射关系 通过result标签设置普通字段的映射关系 column设置映射关系中的字段名,必须是表中的字段 property设置映射关系中的属性名,必须是类型中的属性名 --> <result column="emp_name" property="empName"/> </resultMap> <select id="selectAll" resultMap="empResultMap"> select * from emp </select> |
2、处理多对一的映射关系
通过级联属性赋值
修改Emp.java
public class Emp { private Integer eid; //eid private String empName;//emp_name private Integer age; //age private String job;//job private Double salary; //salary //一个员工只对应一个部门 private Dept dept;//对应此员工的部门信息 //无参+全参+get+set+toString |
准备Dept.java
public class Dept { private Integer id;//id private String deptName; //dept_name //无参+全参+getset+toString |
需求:查询某个员工及其对应的部门信息

添加方法:
//查询某个员工及其对应的部门信息 Emp selectEmpAndDept(@Param("eid") Integer eid); |
sql:
<!-- 查询某个员工及其对应的部门信息Emp selectEmpAndDept(@Param("eid") Integer eid); --> <resultMap type="emp" id="empAndDeptMap"> <result column="id" property="dept.id"/> <result column="dept_name" property="dept.deptName"/> </resultMap> <select id="selectEmpAndDept" resultMap="empAndDeptMap"> select * from emp left join dept on emp.dept_id=dept.id where eid=#{eid}; </select> |
测试:
@Test public voidtestSelectEmpAndDept() { SqlSession sqlSession = SqlSessionUtil.getSqlSession("mybatis-config.xml"); //获取EmpDao接口的实现类对象 EmpDao empDao = sqlSession.getMapper(EmpDao.class); //调用对应方法,执行sql Emp emp = empDao.selectEmpAndDept(1); System.out.println(emp); } |
结果:

通过association处理多对一映射
<!-- 查询某个员工及其对应的部门信息Emp selectEmpAndDept(@Param("eid") Integer eid); --> <resultMap type="emp" id="empAndDeptMap"> <id column="eid" property="eid"/> <result column="emp_name" property="empName"/> <result column="age" property="age"/> <result column="job" property="job"/> <result column="salary" property="salary"/> <!-- 通过association标签处理多对一的映射关系 property:需要处理映射的属性名 javaType:该属性的类型 --> <association property="dept" javaType="dept"> <id column="id" property="id"/> <result column="dept_name" property="deptName"/> </association> </resultMap> <select id="selectEmpAndDept" resultMap="empAndDeptMap"> select * from emp left join dept on emp.dept_id=dept.id where eid=#{eid}; </select> |
通过分步查询处理多对一映射
第一步:查询对应的员工数据
第二步:拿着查出来的该员工对应的部门id到部门表查对应的部门信息
DeptDao:
public interface DeptDao { //第二步:拿着查出来的该员工对应的部门id到部门表查对应的部门信息 Dept selectDeptById(@Param("id") Integer id); } |
DeptMapper
<!-- 第二步:拿着查出来的该员工对应的部门id到部门表查对应的部门信息 Dept selectDeptById(@Param("id") Integer id); --> <select id="selectDeptById" resultType="dept"> select * from dept where id = #{id} </select> |
EmpDao:
//第一步:查询对应的员工数据 Emp selectEmpById(@Param("eid") Integer eid); |
sql:
<resultMap type="emp" id="empAndDeptByStepMap"> <id column="eid" property="eid"/> <result column="emp_name" property="empName"/> <result column="age" property="age"/> <result column="job" property="job"/> <result column="salary" property="salary"/> <!-- 这次的association,是通过另一个查询去映射 也就是将另一个查询的结果,映射到dept属性上 其中 column:是另一个查询的参数 select:指定另一个查询(通过mapper文件的namespace+sql语句的id) --> <association property="dept" select="cn.niit.dao.DeptDao.selectDeptById" column="dept_id"/> </resultMap> <select id="selectEmpAndDept" resultMap="empAndDeptByStepMap"> select * from emp where eid=#{eid} </select> |
结果:执行了2条sql语句

设置延迟加载
分步查询的优点:可以实现延迟加载,但是必须在核心配置文件中设置全局配置
lazyLoadingEnabled:延迟加载的全局开关,当开启时,所有关联对象都会延迟加载
aggressiveLazyLoading:当开启时,任何方法的调用都会加载该对象的所有属性。否则每个属性会按需加载
只要保证lazyLoadingEnabled的值为true,aggressiveLazyLoading的值为false,就可以实现按需加载,获取的数据是什么,就会执行相应的sql。如果某条sql有特殊需求,可以通过设置association和collection标签上的fetchType属性,设置当前的分步查询是否使用延迟加载,fetchTypt=lazy则延迟加载,fetchType=eager则立即加载。
修改核心配置文件:
<!-- MyBatis全局配置 --> <settings> <!-- 可以自动将字段中的_映射为驼峰规则如emp_name:empName --> <setting name="mapUnderscoreToCamelCase" value="true"/> <!-- 设置延迟加载 --> <setting name="lazyLoadingEnabled" value="true"/> </settings> |
测试:
@Test public voidtestSelectEmpAndDept() { SqlSession sqlSession = SqlSessionUtil.getSqlSession("mybatis-config.xml"); //获取EmpDao接口的实现类对象 EmpDao empDao = sqlSession.getMapper(EmpDao.class); //调用对应方法,执行sql Emp emp = empDao.selectEmpAndDept(1); System.out.println(emp.getEmpName()); } |
如果某条分步查询的语句,不希望延迟加载:
<resultMap type="emp" id="empAndDeptByStepMap"> <id column="eid" property="eid"/> <result column="emp_name" property="empName"/> <result column="age" property="age"/> <result column="job" property="job"/> <result column="salary" property="salary"/> <!-- 这次的association,是通过另一个查询去映射 也就是将另一个查询的结果,映射到dept属性上 其中 column:是另一个查询的参数 select:指定另一个查询(通过mapper文件的namespace+sql语句的id) --> <association property="dept" select="cn.niit.dao.DeptDao.selectDeptById" column="dept_id" fetchType="eager"/> </resultMap> <select id="selectEmpAndDept" resultMap="empAndDeptByStepMap"> select * from emp where eid=#{eid} </select> |
3、处理一对多的映射关系
需求:查询某个部门及其对应的所有员工数据

修改Dept.java
public class Dept { private Integer id;//id private String deptName; //dept_name //一个部门对应多个员工 private List<Emp> emps;//表示部门对应的所有员工数据 //无参+全参+getset+toString |
通过collection处理一对多映射
DeptDao:
//查询某个部门及其对应的所有员工 Dept selectDeptAndEmp(@Param("id") Integer id); |
DeptMapper
<!-- 查询某个部门及其对应的所有员工 Dept selectDeptAndEmp(@Param("id") Integer id) --> <resultMap type="dept" id="DeptAndEmpMap"> <id column="id" property="id"/> <result column="dept_name" property="deptName"/> <!-- 通过collection来处理emps属性,ofType:对应的类型是emp(别名) --> <collection property="emps" ofType="emp"> <id column="eid" property="eid"/> <result column="emp_name" property="empName"/> <result column="age" property="age"/> <result column="job" property="job"/> <result column="salary" property="salary"/> </collection> </resultMap> <select id="selectDeptAndEmp" resultMap="DeptAndEmpMap"> select * from dept left join emp on dept.id=emp.dept_id where dept.id = #{id} </select> |
通过分步查询处理一对多映射
需求:查询某个部门及其所有员工的数据
第一步:查询对应部门的数据如查询1号部门的信息
第二步:拿着部门id到员工表中查询对应的员工信息
EmpDao:
//第二步:拿着部门id到员工表中查询对应的员工信息 List<Emp> selectDeptAndEmpByStepTwo(@Param("deptId") Integer deptId); |
EmpMapper
<!-- 第二步:拿着部门id到员工表中查询对应的员工信息 List<Emp> selectDeptAndEmpByStepTwo(@Param("deptId") Integer deptId); --> <select id="selectDeptAndEmpByStepTwo" resultType="emp"> select * from emp where dept_id = #{deptId} </select> |
DeptDao
//第一步:查询对应部门的数据 Dept selectDeptAndEmpByStepOne(@Param("id") Integer id); |
DeptMapper
<resultMap type="dept" id="DeptAndEmpByStepOneMap"> <id column="id" property="id"/> <result column="dept_name" property="deptName"/> <!-- 通过collection来处理emps属性,设置拿着哪一列的值作为参数 去执行哪条sql,将结果映射到此emps属性中 --> <collection property="emps" select="cn.niit.dao.EmpDao.selectDeptAndEmpByStepTwo" column="id"/> </resultMap> <!-- 第一步:查询对应部门的数据 Dept selectDeptAndEmpByStepOne(@Param("id") Integer id); --> <select id="selectDeptAndEmpByStepOne" resultMap="DeptAndEmpByStepOneMap"> select * from dept where id = #{id} </select> |
测试:
@Test public voidtestSelectDeptAndEmp2() { SqlSession sqlSession = SqlSessionUtil.getSqlSession("mybatis-config.xml"); //获取EmpDao接口的实现类对象 DeptDao deptDao = sqlSession.getMapper(DeptDao.class); //调用对应方法,执行sql Dept dept = deptDao.selectDeptAndEmpByStepOne(1);//查1号部门及其员工数据 System.out.println(dept); } |
结果:

动态SQL
Mybatis框架的动态SQL技术是一种根据特定条件拼接SQL语句的功能,它存在的意义是为了解决拼接SQL语句字符串时的痛点问题。
1、if标签
mybatis中的if元素用于对某一字段进行判断,从而决定是否执行包含在其中的SQL片段。
需求:

查询emp表中所有员工的信息,另:
如果传递了minSal(最低薪资)和maxSal(最高薪资),则查询薪资大于minSal和小于maxSal的员工信息;
如果只传递了minSal,则查询薪资大于minSal的所有员工信息;
如果只传递了maxSal,则查询薪资小于maxSal的所有员工信息;
接口中的方法:
public interface EmpDao { //根据多条件查询员工数据 List<Emp> selectEmpByMoreSal(@Param("minSal") Double minSal,@Param("maxSal") Double maxSal); } |
sql:
<!-- 根据多条件查询员工数据 List<Emp> selectEmpByMoreSal (@Param("minSal") Double minSal,@Param("maxSal") Double maxSal); 如果minSal和maxSal都没传 : select * from emp 如果只传了minSal : select * from emp where salary > minSal 如果只传了maxSal : select * from emp where salary < maxSal 如果传了minSal和maxSal : select * from emp where salary > minSal and salary < maxSal 通过动态SQL标签if,实现,用1条SQL动态地实现这四个需求 if标签,可以判断,如果条件为true则if标签内的sql片段会生效,否则不生效 注意需要用<表示< --> <select id="selectEmpByMoreSal" resultType="emp"> select * from emp where 1=1 <if test="minSal != null and minSal != ''"> <!-- test表达式的结果为true,if标签内的sql才会生效 --> salary > #{minSal} </if> <if test="maxSal != null and maxSal != ''"> <!-- test表达式的结果为true,if标签内的sql才会生效 --> and salary < #{maxSal} </if> </select> |
2、where标签
where元素则用于对包含在其中的SQL语句进行检索,需要时会添加where关键字,还会剔除多余的连接词(比如and或者or),但是,where标签只能剔除内容前的and或or。
<select id="selectEmpByMoreSal" resultType="emp"> select * from emp <where> <if test="minSal != null and minSal != ''"> <!-- test表达式的结果为true,if标签内的sql才会生效 --> salary > #{minSal} </if> <if test="maxSal != null and maxSal != ''"> <!-- test表达式的结果为true,if标签内的sql才会生效 --> and salary < #{maxSal} </if> </where> </select> |
3、trim标签
prefix|suffix : 在trim标签内容前后添加指定内容
suffixOverrides|prefixOverrides : 在trim标签内容前后去掉指定内容
可以去除前面的连接词:
<select id="selectEmpByMoreSal" resultType="emp"> select * from emp <trim prefix="where" prefixOverrides="and|or"> <if test="minSal != null and minSal != ''"> <!-- test表达式的结果为true,if标签内的sql才会生效 --> salary > #{minSal} </if> <if test="maxSal != null and maxSal != ''"> <!-- test表达式的结果为true,if标签内的sql才会生效 --> and salary < #{maxSal} </if> </trim> </select> |
也可以去掉后面的连接词:
<select id="selectEmpByMoreSal" resultType="emp"> select * from emp <trim prefix="where" suffixOverrides="and|or"> <if test="minSal != null and minSal != ''"> <!-- test表达式的结果为true,if标签内的sql才会生效 --> salary > #{minSal} and </if> <if test="maxSal != null and maxSal != ''"> <!-- test表达式的结果为true,if标签内的sql才会生效 --> salary < #{maxSal} </if> </trim> </select> |
4、choose、when、otherwise
这是一组标签,相当于if..else if… else
和前面不同的是,它只会执行其中某一个sql片段
<select id="selectEmpByMoreSal" resultType="emp"> select * from emp <trim prefix="where" suffixOverrides="and|or"> <choose> <when test="minSal != null and minSal != ''"> salary > #{minSal} </when> <when test="maxSal != null and maxSal != ''"> salary < #{maxSal} </when> <otherwise> salary > 3000 </otherwise> </choose> </trim> </select> |
when至少一个,otherwise最多一个
choose中的sql片段,最多只会选择其中一个执行
5、for-each标签
collection属性:设置需要循环的数组或集合
item属性:表示数组或集合中的每一个元素
separator:循环体之间的分割符
open:foreach标签所循环的所有内容的开始符
close:foreach标签所循环的所有内容的的结束符
实现批量删除
方法:
//实现批量删除 int deleteBatch(@Param("ids") Integer[] ids); |
sql:
<!-- 实现批量删除int deleteBatch(@Param("ids") Integer[] ids); --> <delete id="deleteBatch"> delete from emp where eid in <foreach collection="ids" item="id" separator="," open="(" close=")"> #{id} </foreach> </delete> |
测试:
@Test public void testDeleteBatch(){ SqlSession sqlSession = SqlSessionUtil.getSqlSession("mybatis-config.xml"); EmpDao empDao = sqlSession.getMapper(EmpDao.class); int rows = empDao.deleteBatch(new Integer[]{1,2}); System.out.println("受影响的行数:"+rows); } |
通过or实现批量删除
<!-- 实现批量删除int deleteBatch(@Param("ids") Integer[] ids); --> <delete id="deleteBatch"> delete from emp where <foreach collection="ids" item="id" separator="or" > eid = #{id} </foreach> </delete> |
实现批量添加
方法:
//实现批量添加 intinsertBatch(@Param("emps") List<Emp> emps); |
sql:
<!-- 实现批量添加int insertBatch(@Param("emps") List<Emp> emps); --> <insert id="insertBatch"> insert into emp values <foreach collection="emps" item="emp" separator=","> (null,#{emp.empName},#{emp.age},#{emp.job},#{emp.salary},null) </foreach> </insert> |
6、sql标签
通过sql标签设置sql片段
通过include标签引用sql片段
<sql id="empCols"> eid,emp_name,age,job,salary,dept_id </sql> <select id="selectEmpByMoreSal" resultType="emp"> select <include refid="empCols"/> from emp <trim prefix="where" suffixOverrides="and|or"> <choose> <when test="minSal != null and minSal != ''"> salary > #{minSal} </when> <when test="maxSal != null and maxSal != ''"> salary < #{maxSal} </when> <otherwise> salary > 3000 </otherwise> </choose> </trim> </select> |
MyBatis的缓存

1、MyBatis一级缓存
MyBatis的一级缓存是SqlSession级别的,即通过同一个SqlSession查询的数据会被缓存,下次查询相同的数据时,就会从缓存中直接获取,不会重新访问数据库。
一级缓存失效的四种情况:
不是同一个SqlSession
是同一个SqlSession但是查询条件不同
是同一个SqlSession,但是两次查询期间执行了任何一次的增删改操作
是同一个SqlSession但是两次查询期间,手动清空了缓存
测试:
一级缓存是默认开启的,只要是同一个SqlSession,执行相同查询时,第二次不会访问数据库执行sql,而是直接从缓存中获取。
方法:
//根据id查询员工数据 Emp selectById(@Param("id") Integer id); |
sql:
<!-- 根据id查询员工数据Emp selectById(@Param("id") Integer id); --> <select id="selectById" resultType="emp"> select <include refid="empCols"/> from emp where eid = #{id} </select> |
测试:
@Test public voidtestCache() { SqlSession sqlSession = SqlSessionUtil.getSqlSession("mybatis-config.xml"); //通过sqlSession获取empDao1,执行同一条查询语句 EmpDao empDao1 = sqlSession.getMapper(EmpDao.class); Emp emp1 = empDao1.selectById(5); //通过同一个sqlSession获取empDao2,执行同一条查询语句 EmpDao empDao2 = sqlSession.getMapper(EmpDao.class); Emp emp2 = empDao2.selectById(5); //打印结果 System.out.println(emp1); System.out.println(emp2); } |
结果:

两次查询都有结果,但是只执行了一次sql语句,第二次查询的结果是从缓存获取的。
测试不同sqlSession,缓存失效
@Test public voidtestCache() { //获取两个不同的sqlSession SqlSession sqlSession1 = SqlSessionUtil.getSqlSession("mybatis-config.xml"); SqlSession sqlSession2 = SqlSessionUtil.getSqlSession("mybatis-config.xml"); //获取接口对象,执行对应sql EmpDao empDao1 = sqlSession1.getMapper(EmpDao.class); EmpDao empDao2 = sqlSession2.getMapper(EmpDao.class); //执行相同sql Emp emp1 = empDao1.selectById(1); Emp emp2 = empDao2.selectById(1); //打印结果 System.out.println(emp1); System.out.println(emp2); } |
结果:

测试两次查询间进行了新增员工信息的操作:
方法:
//新增员工信息 int insertEmp(Emp emp); |
sql:
<!-- 新增员工信息int insertEmp(Empemp); --> <insert id="insertEmp"> insert into emp values(null,#{empName},#{age},#{job},#{salary},null) </insert> |
测试:
@Test public voidtestCache() { //获取sqlSession SqlSession sqlSession = SqlSessionUtil.getSqlSession("mybatis-config.xml"); //获取接口对象,执行对应sql EmpDao empDao = sqlSession.getMapper(EmpDao.class); //同一个SqlSession的情况下执行相同sql Emp emp1 = empDao.selectById(1); //两次查询直接做新增操作,则缓存失效 empDao.insertEmp(new Emp(null, "孙策", 18, "销售", 3000.0)); Emp emp2 = empDao.selectById(1); //打印结果 System.out.println(emp1); System.out.println(emp2); } |
结果:

数据有了改变,为了保证数据的正确,缓存会失效,所以第二次查询查的是数据库
测试手动清空缓存:
@Test public void testCache() { //获取sqlSession SqlSession sqlSession = SqlSessionUtil.getSqlSession("mybatis-config.xml"); //获取接口对象,执行对应sql EmpDao empDao = sqlSession.getMapper(EmpDao.class); //同一个SqlSession的情况下执行相同sql Emp emp1 = empDao.selectById(5); //手动清空缓存 sqlSession.clearCache(); Emp emp2 = empDao.selectById(5); //打印结果 System.out.println(emp1); System.out.println(emp2); } |

2、MyBatis二级缓存
二级缓存是SqlSessionFactory级别的,即通过同一个SqlSessionFactory创建的SqlSession查询的结果会被缓存,此后若再执行相同的查询语句,会从缓存中获取结果
二级缓存开启的条件:
在核心配置文件中,设置全局配置属性cacheEnabled=true,默认为true,不用配置
在映射文件中设置<cache/>标签
二级缓存必须在SqlSession关闭或者提交之后有效
查询的数据所转换的实体类类型必须实现序列化接口
使二级缓存失效的情况:
两次查询之间执行了任意的增删改,会使一级和二级缓存同时失效
在映射文件中设置<cache/>标签:
<mapper namespace="cn.niit.dao.EmpDao"> <cache/> |
实体类实现序列化接口:
public classEmpimplementsSerializable{ private Integer eid; //eid private String empName;//emp_name private Integer age; //age private String job;//job private Double salary; //salary |
测试:
@Test public void testCache2() { try { InputStream in = Resources.getResourceAsStream("mybatis-config.xml"); SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder(); //获取相同的sqlSessionFactory SqlSessionFactory fac = builder.build(in); //获取不同的sqlSession SqlSession sqlSession1 = fac.openSession(true); SqlSession sqlSession2 = fac.openSession(true); //获取接口的实现类对象 EmpDao empDao1 = sqlSession1.getMapper(EmpDao.class); EmpDao empDao2 = sqlSession2.getMapper(EmpDao.class); //执行相同sql Emp emp1 = empDao1.selectById(5); //sqlSession1执行完此sql必须提交或关闭,才会将查询的数据提交到缓存中 //sqlSession1.commit(); sqlSession1.close(); Emp emp2 = empDao2.selectById(5); //打印结果 System.out.println(emp1); System.out.println(emp2); } catch (IOException e) { e.printStackTrace(); } } |
3、缓存查询的顺序
先查询二级缓存,因为二级缓存中可能会有其他程序已经查出来的数据,可以拿来直接使用。
如果二级缓存没有命中,再查询一级缓存
如果一级缓存也没有命中,则查询数据库
SqlSession关闭或提交后之后,一级缓存中的数据会写入二级缓存
4、二级缓存的相关配置
在mapper配置文件中添加的cache标签可以设置一些属性:
eviction属性:缓存回收策略,默认的是 LRU。
LRU(Least RecentlyUsed) – 最近最少使用:移除最长时间不被使用的对象。
FIFO(First in Firstout) – 先进先出:按对象进入缓存的顺序来移除它们。
flushInterval属性:刷新间隔,单位毫秒
默认情况是不设置,也就是没有刷新间隔,缓存仅仅调用语句时刷新
size属性:引用数目,正整数
代表缓存最多可以存储多少个对象,太大容易导致内存溢出
readOnly属性:只读, true/false
true:只读缓存;会给所有调用者返回缓存对象的相同实例。因此这些对象不能被修改。这提供了很重要的性能优势。
false:读写缓存;会返回缓存对象的拷贝(通过序列化)。这会慢一些,但是安全,因此默认是false。
MyBatis的逆向工程
正向工程:先创建java实体类,由框架根据实体类生成数据库表。Hibernate是支持正向工程的。
逆向工程:先创建数据库表,由框架负责根据数据库表,反向生成如下资源:
java实体类
Dao接口
Mapper映射文件
1、创建逆向工程的步骤
添加mybatis-generator插件pom
<!-- 控制maven在构建过程中的相关配置 --> <build> <!-- 构建过程中用到的插件 --> <plugins> <!-- 具体的插件 --> <plugin> <groupId>org.mybatis.generator</groupId> <artifactId>mybatis-generator-maven-plugin</artifactId> <version>1.3.2</version> <!-- 插件的依赖 --> <dependencies> <!-- 逆向工程的核心依赖 --> <dependency> <groupId>org.mybatis.generator</groupId> <artifactId>mybatis-generator-core</artifactId> <version>1.3.2</version> </dependency> <!-- 数据库连接池 --> <dependency> <groupId>com.mchange</groupId> <artifactId>c3p0</artifactId> <version>0.9.5.5</version> </dependency> <!-- mysql驱动 --> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>5.1.32</version> </dependency> </dependencies> </plugin> </plugins> </build> |
创建逆向工程的配置文件 generatorConfig.xml(文件名不能改)

<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE generatorConfiguration PUBLIC "-//mybatis.org//DTD MyBatis Generator Configuration 1.0//EN" "" target="_blank">http://mybatis.org/dtd/mybatis-generator-config_1_0.dtd"> <generatorConfiguration> <!-- targetRuntime:执行生成的逆向工程的版本 MyBatis3Simple:生成基本的CRUD(清新简洁版) MyBatis3:生成带条件的CRUD(奢华尊享版) --> <context id="DB2Tables" targetRuntime="MyBatis3"> <!--数据库链接URL,用户名、密码yonghedb表示数据库名称,用户和密码别填错了! --> <jdbcConnection driverClass="com.mysql.jdbc.Driver" connectionURL="jdbc:mysql://localhost:3306/shenmu?characterEncoding=utf-8" userId="root" password="root"> </jdbcConnection> <!-- 生成实体类的包名和位置,路径可自定义指定 --> <javaModelGenerator targetPackage="cn.niit.pojo" targetProject=".\src\main\java"> <property name="enableSubPackages" value="true" /> <property name="trimStrings" value="true" /> </javaModelGenerator> <!-- 生成映射文件的包名和位置,路径可自定义指定 --> <sqlMapGenerator targetPackage="mapper" targetProject=".\src\main\resources"> <property name="enableSubPackages" value="true" /> </sqlMapGenerator> <!-- 生成接口的包名和位置,路径可自定义指定 --> <javaClientGenerator type="XMLMAPPER" targetPackage="cn.niit.dao" targetProject=".\src\main\java"> <property name="enableSubPackages" value="true" /> </javaClientGenerator>
<!-- 逆向分析的表tableName是数据库中的表名或视图名,可设置为*对应所有表,此时不写domainObjectName domainObjectName是实体类名 --> <table tableName="emp" domainObjectName="Emp"/> <table tableName="dept" domainObjectName="Dept"/> </context> </generatorConfiguration>
|
执行插件

输入如下内容后点击run
mybatis-generator:generate


成功后右键项目刷新即可

2、把生成的文件删掉,测试QBC查询
为了测试,准备mybatis的环境
添加依赖:
<dependencies> <!-- junit单元测试 --> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.12</version> </dependency> <!-- mybatis --> <dependency> <groupId>org.mybatis</groupId> <artifactId>mybatis</artifactId> <version>3.5.11</version> </dependency> <dependency> <groupId>org.slf4j</groupId> <artifactId>slf4j-log4j12</artifactId> <version>1.6.4</version> </dependency> <!-- mysql驱动 --> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>5.1.32</version> </dependency> </dependencies> |
准备mybayis核心配置文件,log4j.properties,jdbc.properties

这些都是自动生成的
其中Example.java文件是为多条件查询服务的。
测试查询所有员工数据
@Test public void testMBG(){ try { InputStream in = Resources.getResourceAsStream("mybatis-config.xml"); SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(in); SqlSession sqlSession = factory.openSession(true); EmpMapper empMapper = sqlSession.getMapper(EmpMapper.class); //查询所有数据 List<Emp> emps = empMapper.selectByExample(null); System.out.println(emps);//记得生成toString才能看到属性值 } catch (IOException e) { e.printStackTrace(); } } |
测试多条件查询
查询姓名为张三,并且职位为程序员的员工信息
@Test public void testMBG(){ try { InputStream in = Resources.getResourceAsStream("mybatis-config.xml"); SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(in); SqlSession sqlSession = factory.openSession(true); EmpMapper empMapper = sqlSession.getMapper(EmpMapper.class); //设置条件EmpExample EmpExample empExample = new EmpExample(); empExample.createCriteria().andEmpNameEqualTo("张三").andJobEqualTo("程序员"); //执行多条件查询 List<Emp> emps = empMapper.selectByExample(empExample); System.out.println(emps); } catch (IOException e) { e.printStackTrace(); } } |
测试:查询姓名为张三,并且职位为程序员 或者 薪资大于等于3000 的员工信息
@Test public void testMBG(){ try { InputStream in = Resources.getResourceAsStream("mybatis-config.xml"); SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(in); SqlSession sqlSession = factory.openSession(true); EmpMapper empMapper = sqlSession.getMapper(EmpMapper.class); //设置条件EmpExample EmpExample empExample = new EmpExample(); //姓名为张三且职位为程序员 empExample.createCriteria().andEmpNameEqualTo("张三").andJobEqualTo("程序员"); //或者薪资大于等于3000 empExample.or().andSalaryGreaterThanOrEqualTo(3000.0); //执行多条件查询 List<Emp> emps = empMapper.selectByExample(empExample); System.out.println(emps); } catch (IOException e) { e.printStackTrace(); } } |
测试更新员工数据
//无参全参构造 public Emp() { super(); } public Emp(Integer eid, String empName, Integerage, String job, Double salary, Integer deptId) { super(); this.eid = eid; this.empName = empName; this.age = age; this.job = job; this.salary = salary; this.deptId = deptId; } |

测试updateByPrimaryKeySelective
@Test public void testMBG(){ try { InputStream in = Resources.getResourceAsStream("mybatis-config.xml"); SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(in); SqlSession sqlSession = factory.openSession(true); EmpMapper empMapper = sqlSession.getMapper(EmpMapper.class); //修改员工数据,不想改的列属性给null值即可 int rows = empMapper.updateByPrimaryKeySelective(new Emp(6, "孙权", null, null, 4000.0, 1)); System.out.println("影响的行数:"+rows); } catch (IOException e) { e.printStackTrace(); } } |
测试updateByPrimaryKey
@Test public void testMBG(){ try { InputStream in = Resources.getResourceAsStream("mybatis-config.xml"); SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(in); SqlSession sqlSession = factory.openSession(true); EmpMapper empMapper = sqlSession.getMapper(EmpMapper.class); //修改员工数据,全都会改,给null值的地方,对应列会改为null值 int rows = empMapper.updateByPrimaryKey(new Emp(6, "孙权", null, null, 4000.0, 1)); System.out.println("影响的行数:"+rows); } catch (IOException e) { e.printStackTrace(); } } |
分页插件PageHelper
1、环境布置
1)添加依赖
<dependency> <groupId>com.github.pagehelper</groupId> <artifactId>pagehelper</artifactId> <version>5.3.2</version> </dependency> |
2)在mybatis核心配置文件中配置分页插件
<plugins> <plugin interceptor="com.github.pagehelper.PageInterceptor"/> </plugins> |
2、使用分页插件
1)要在查询之前开启分页
Page<Object> page =PageHelper.startPage(pageNum, pageSize);
2)查询后可以拿到分页信息pageInfo
PageInfo<Emp> pageInfo = newPageInfo<>(list, navigatePages);
list表示分页数据
navigatePages表示导航分页的数量
/* * 回忆分页查询 * select * from emp limit startIndex,count; * limit 可以获取中间数据, * startIndex表示起始索引,从0开始,表示从哪条sql开始 * count表示要几条数据 * 因此通过limit实现分页查询时, * pageSize:页面大小,订好的,比如每页放10条数据 * pageNum:当前页的页码 * 分页算法: * limit (pageSize-1)*pageNum,pageSize */ @Test public void testPageHelper(){ try { InputStream in = Resources.getResourceAsStream("mybatis-config.xml"); SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(in); SqlSession sqlSession = factory.openSession(true); EmpMapper empMapper = sqlSession.getMapper(EmpMapper.class); //查询员工数据,分页查询 PageHelper.startPage(2, 5);//2表示要查询第几页的数据,5表示页面大小 List<Emp> emps = empMapper.selectByExample(null); System.out.println(emps); } catch (IOException e) { e.printStackTrace(); } } |
查看page中的内容
@Test public void testPageHelper(){ try { InputStream in = Resources.getResourceAsStream("mybatis-config.xml"); SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(in); SqlSession sqlSession = factory.openSession(true); EmpMapper empMapper = sqlSession.getMapper(EmpMapper.class); //查询员工数据,分页查询 Page<Object> page = PageHelper.startPage(2, 5);//2表示要查询第几页的数据,5表示页面大小 System.out.println(page);//看下page中的内容 List<Emp> emps = empMapper.selectByExample(null); System.out.println(emps); } catch (IOException e) { e.printStackTrace(); } } |
Page{count=true, pageNum=2,pageSize=5, startRow=5, endRow=10,
total=41, pages=9, reasonable=false,pageSizeZero=false}
查看pageInfo中的内容:
@Test public voidtestPageHelper(){ try { InputStream in = Resources.getResourceAsStream("mybatis-config.xml"); SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(in); SqlSession sqlSession = factory.openSession(true); EmpMapper empMapper = sqlSession.getMapper(EmpMapper.class); //查询员工数据,分页查询 Page<Object> page = PageHelper.startPage(2, 5);//2表示要查询第几页的数据,5表示页面大小 //System.out.println(page);//看下page中的内容 List<Emp> emps = empMapper.selectByExample(null); //查询后可以通过结果,获取一个对应的pageInfo对象 PageInfo<Emp> pageInfo = new PageInfo<>(emps, 5); System.out.println(pageInfo); //System.out.println(emps); } catch (IOException e) { e.printStackTrace(); } } |
PageInfo{pageNum=2, pageSize=5, size=5(真实数据数量),
startRow=6, endRow=10,
total=41, pages=9, prePage=1,nextPage=3,
isFirstPage=false, isLastPage=false,hasPreviousPage=true, hasNextPage=true,navigatePages=5,navigateFirstPage=1,navigateLastPage=5,
navigatepageNums=[1, 2, 3, 4, 5]}