Mybatis基础
包括:Mybatis、Mybatis-generator自动生成代码。
Mybatis
注意: maven3.6与IDEA2018版本不匹配,所以使用maven3.5.2
下载地址:https://github.com/mybatis/mybatis-3/releases
1.1 什么是 MyBatis?
- MyBatis 是一款优秀的持久层框架
- 它支持自定义 SQL、存储过程以及高级映射。
- MyBatis 免除了几乎所有的 JDBC 代码以及设置参数和获取结果集的工作。
- MyBatis 可以通过简单的 XML 或注解来配置和映射原始类型、接口和 Java POJO(Plain Old Java Objects,普通老式 Java 对象)为数据库中的记录。
1.2 Mybatis快速入门
思路:搭建环境-->导入需要的包-->编写代码-->测试
- 前三个在之后的程序编写中不用改动

1.2.1 导入依赖
使用 Maven 来构建项目,将下面的依赖代码置于 pom.xml 文件中:
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.4.5</version>
</dependency>
1.2.2 编写配置文件
- 配置文件的作用: 配置连接数据库所需要的数据源(url、driver、username、password)。
mybatis-config.xml
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
<!--mybatis核心配置文件-->
<configuration>
<!--可以包含多个环境,default选择使用哪个环境-->
<environments default="mybatis">
<environment id="mybatis">
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<property name="driver" value="com.mysql.jdbc.Driver"/>
<!--在xml中,特殊字符需要转义&相当于&-->
<property name="url" value="jdbc:mysql://localhost:3306/mybatis?useSSL=true&
useUnicode=true&charsetEncoding=utf-8"/>
<property name="username" value="root"/>
<property name="password" value="root"/>
</dataSource>
</environment>
</environments>
<mappers>
<mapper resource="cn/rm/dao/UserMapper.xml"/>
</mappers>
</configuration>
1.2.3 编写MyBatis工具类
工具类的作用:
- 加载配置文件
- 获取执行sql的对象sqlSession
public class MybatisUtil {
private static SqlSessionFactory sqlSessionFactory;
//静态代码块,类加载的同时获取到sqlSessionFactory
static {
InputStream inputStream =null;
try {
//根据流得到sqlSessionFactory(根据工厂建造者对象获得)
String resource = "mybatis-config.xml";
inputStream = Resources.getResourceAsStream(resource);
sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
} catch (IOException e) {
e.printStackTrace();
}finally {
//关闭流,释放资源
if(inputStream!=null){
try {
inputStream.close();
} catch (IOException e) {
}
}
}
}
//创建一个获取sqlSession对象的方法(根据工厂获得)
//sqlSession用来执行sql语句
public static SqlSession getSqlSession(){
SqlSession session = sqlSessionFactory.openSession();
return session;
}
}
1.2.4 编写Java代码
实体类
//创建实体类,对应数据库中的user表
public class User {
private int id;
private String name;
private String password;
//...
//构造器,getset方法,toString方法
}
Dao接口(Mapper接口)
//操作数据库中user表的接口
public interface UserMapper {
//查找所有的用户
List<User> findAll();
}
接口实现类(UserMapper.xml)
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<!--该配置文件相当于过去的接口实现类-->
<!--nameSpace命名空间,绑定一个指定的Mapper(过去的Dao接口)-->
<mapper namespace="cn.rm.dao.UserMapper">
<!--id属性对应着接口实现类的方法名-->
<select id="findAll" resultType="cn.rm.pojo.User">
select * from user
</select>
</mapper>
1.2.5 测试
资源导出问题
由于maven约定大于配置,之后可能遇到写的配置文件不在指定的包下,无法导出或者生效的问题。
解决方案,在pom.xml中配置
<build>
<resources>
<resource>
<directory>src/main/java</directory>
<includes>
<include>**/*.properties</include>
<include>**/*.xml</include>
</includes>
<filtering>false</filtering>
</resource>
</resources>
</build>
UserMapperTest
public class UserMapperTest {
@Test
public void findAll(){
//获取sqlSession对象
SqlSession sqlsession = MybatisUtil.getSqlSession();
//执行sql语句
UserMapper mapper = sqlsession.getMapper(UserMapper.class);
List<User> users = mapper.findAll();
for (User user : users) {
System.out.println(user);
}
sqlsession.close();
}
}
测试结果
User{id=1, name='zhangsan', password='123456'}
User{id=2, name='lisi', password='123456'}
User{id=3, name='wangwu', password='123456'}
User{id=4, name='麻子', password='123456'}
User{id=8, name='二狗', password='23654125'}
1.3 Mybatis实现增删改查
mybatis配置文件:
- namespace:包名要和Mapper接口一致。
- id:对应的namespace中的方法名
- resultType:sql语句执行后的返回值
- parameterType:参数类型
注意事项
- 增删改业务需要提交事务
接口
//操作数据库中user表的接口
public interface UserMapper {
//查找所有的用户
List<User> findAll();
//根据id查询用户
User getUserById(int id);
//插入一个用户,需要传递的参数是User类型
int addUser(User user);
//修改一个用户
int updateUser(User user);
//删除一个用户,只需要传入id即可
int deleteUser(int id);
}
mapper.xml
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<!--该配置文件相当于过去的接口实现类-->
<!--nameSpace命名空间,绑定一个指定的Mapper(过去的Dao接口)-->
<mapper namespace="cn.rm.dao.UserMapper">
<!--id属性对应着接口实现类的方法名-->
<select id="findAll" resultType="cn.rm.pojo.User">
select * from user
</select>
<!--根据id查询对应的用户-->
<select id="getUserById" resultType="cn.rm.pojo.User" parameterType="int" >
select *from user where id = #{id}
</select>
<!--插入语句需要使用insert标签-->
<!--添加一个User,pojo中的User类中的属性可以直接获取-->
<insert id="addUser" parameterType="cn.rm.pojo.User">
insert into mybatis.user (id,name,password) values (#{id},#{name},#{password})
</insert>
<!--修改一个用户-->
<update id="updateUser" parameterType="cn.rm.pojo.User">
update mybatis.user set name=#{name},password=#{password} where id=#{id} ;
</update>
<!--删除一个用户-->
<delete id="deleteUser" parameterType="int">
delete from mybatis.user where id =#{id}
</delete>
</mapper>
测试
public class UserMapperTest {
@Test
public void findAll(){
//获取sqlSession对象
SqlSession sqlsession = MybatisUtil.getSqlSession();
//执行sql语句
UserMapper mapper = sqlsession.getMapper(UserMapper.class);
List<User> users = mapper.findAll();
for (User user : users) {
System.out.println(user);
}
sqlsession.close();
}
@Test
public void getUserById(){
//获取sqlSession对象
SqlSession sqlsession = MybatisUtil.getSqlSession();
//执行sql语句
UserMapper mapper = sqlsession.getMapper(UserMapper.class);
User user = mapper.getUserById(1);
System.out.println(user);
sqlsession.close();
}
@Test
public void addUser(){
SqlSession sqlSession = MybatisUtil.getSqlSession();
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
User user = new User(7, "强子", "12345678");
int i = mapper.addUser(user);
if(i>0){
System.out.println("插入成功");
}
//增删改需要提交事务,才能成功(执行sql的对象开启事务)
sqlSession.commit();
sqlSession.close();
}
@Test
public void updateUser(){
SqlSession sqlSession = MybatisUtil.getSqlSession();
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
User user = new User(7, "强哥", "1111111");
int i = mapper.updateUser(user);
if(i>0){
System.out.println("更新成功");
}
sqlSession.commit();
sqlSession.close();
}
@Test
public void deleteUser(){
SqlSession sqlSession = MybatisUtil.getSqlSession();
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
int i = mapper.deleteUser(5);
if(i>0){
System.out.println("删除成功");
}
sqlSession.commit();
sqlSession.close();
}
}
错误分析
- 标签不要匹配错,插入语句使用insert标签
- resource绑定mapper,需要使用路径/
- 程序配置文件需要符合规范
- 空指针异常,没有注册到资源
- maven资源没有导出问题
1.3.1 万能的Map
实体类或者数据库中的表,字段或者属性过多时,有些有默认值的不需要传参,应该考虑使用Map
- 修改用户的一个属性,不需要传递整个对象
- sql语句中的参数对应着map中的key
- map的value就对应着实际传递进去的参数
UserMapper
//修改一个用户,传递Map参数
int updateUser2(Map<String,Object> map);
UserMapper.xml
<!--修改一个用户,传递Map的key,测试的时候传递map对应key的值-->
<update id="updateUser2" parameterType="map">
update mybatis.user set name=#{name} where id=#{id} ;
</update>
Test
@Test
public void updateUser2(){
SqlSession sqlSession = MybatisUtil.getSqlSession();
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
HashMap<String, Object> map = new HashMap<String, Object>();
map.put("name","诸葛青");
map.put("id",3);
int i = mapper.updateUser2(map);
if(i>0){
System.out.println("修改成功");
}
sqlSession.commit();
sqlSession.close();
}
- Map传递参数,直接在sql中取出key即可【parameterType="map"】
- 对象传递参数,直接在sql中取对象的属性即可【parameterType="Object"】
- 只有一个基本类型参数的情况下,在sql中可以直接获取
1.3.2 模糊查询
1. Java代码执行的时候,传递通配符% %
@Test
public void getLikeUser(){
SqlSession sqlSession = MybatisUtil.getSqlSession();
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
List<User> users = mapper.getLikeUser("%强%");
for (User user : users) {
System.out.println(user);
}
sqlSession.close();
}
2. 在sql拼接中使用通配符
<!--模糊查询-->
<select id="getLikeUser" resultType="cn.rm.pojo.User">
select * from mybatis.user where name like "%"#{value}"%"
</select>
1.4 Mybatis配置解析
mybatis-config.xml 这一文件可以配置的内容。
- properties(属性)
- settings(设置)
- typeAliases(类型别名)
- typeHandlers(类型处理器)
- objectFactory(对象工厂)
- plugins(插件)
- environments(环境配置)
- environment(环境变量)
- transactionManager(事务管理器)
- dataSource(数据源)
- databaseIdProvider(数据库厂商标识)
- mappers(映射器)
在xml配置文件中,标签顺序必须符合上面的标签顺序,properties最前面,mappers最后面
1.4.1 properties
外部配置
- 创建db.properties文件, 包含连接数据库的四个基本信息,driver,url,username,password,通过resources属性引入外部资源文件
- 之后就可以在数据源中使用${键名}来代替具体的内容
<properties resource="db.properties"/>
- 创建properties文件
driver=com.mysql.jdbc.Driver
url=jdbc:mysql://localhost:3306/mybatis?useSSL=true&charsetEncoding=utf-8&useUnicode=true
name=root
password=root
- 在mybatis-config中引入外部文件
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
<!--mybatis核心配置文件-->
<configuration>
<!--引入外部properties文件-->
<properties resource="db.properties"/>
<environments default="mybatis">
<environment id="mybatis">
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<property name="driver" value="${driver}"/>
<!--在xml中,特殊字符需要转义&相当于&-->
<property name="url" value="${url}"/>
<property name="username" value="${name}"/>
<property name="password" value="${password}"/>
</dataSource>
</environment>
</environments>
<mappers>
<mapper resource="cn/rm/mapper/UserMapper.xml"/>
</mappers>
</configuration>
内部配置
- 在properties中添加键值对信息
- 如果两个文件有同一字段,
优先使用外部配置文件的字段。
<properties resource="db.properties">
<property name="username" value="root"/>
<property name="password" value="root"/>
</properties>
1.4.2 settings
这是 MyBatis 中极为重要的调整设置,它们会改变 MyBatis 的运行时行为。
cacheEnabled
- 全局性地开启或关闭所有映射器配置文件中已配置的任何缓存。
- 默认true
lazyLoadingEnabled
- 延迟加载的全局开关。当开启时,所有关联对象都会延迟加载。 特定关联关系中可通过设置 fetchType 属性来覆盖该项的开关状态。
- 默认false
logImpl(日志实现)
-
指定 MyBatis 所用日志的具体实现,未指定时将自动查找。
-
可选择:
SLF4J
LOG4J 【掌握】
LOG4J2
JDK_LOGGING
COMMONS_LOGGING
STDOUT_LOGGING 【掌握】
NO_LOGGING
mapUnderscoreToCamelCase
- 是否开启驼峰命名自动映射,即从经典数据库列名 A_COLUMN 映射到经典 Java 属性名 aColumn。
- 默认false
- 因为oracle会把字段全部转换为大写,为方便阅读,使用_分开单词,如:last_name
1.4.3 typeAliases
类型别名可为 Java 类型设置一个缩写名字。 它仅用于 XML 配置,意在降低冗余的全限定类名书写。
- 当这样配置时,Blog 可以用在任何使用 domain.blog.Blog 的地方。
<typeAliases>
<typeAlias alias="Author" type="domain.blog.Author"/>
<typeAlias alias="Blog" type="domain.blog.Blog"/>
<typeAlias alias="Comment" type="domain.blog.Comment"/>
</typeAliases>
- 也可以指定一个包名,MyBatis 会在包名下面搜索需要的 Java Bean,比如:
<typeAliases>
<package name="cn.rm.pojo"/>
</typeAliases>
- 每一个在包 domain.blog 中的 Java Bean,在没有注解的情况下,会使用 Bean 的首字母小写的非限定类名来作为它的别名。 比如 cn.rm.pojo.User 的别名为 user;若有注解,则别名为其注解值。见下面的例子:
@Alias("user")
public class User{
...
}
注意
- 在实体类比较少时,使用第一种,在实体类比较多时,使用第二种
- 如果返回值类型或者参数是基本类型,需要在前面加_,比如别名_int就表示返回值类型是int。
- 第一种可以DIY别名,第二种如果想要改别名,需要在实体类上面添加@Alias注解,则别名为其注解值。下面的例子:
@Alias("author")
public class Author {
...
}
1.4.4 enviroments
-
MyBatis 可以配置成适应多种环境,这种机制有助于将 SQL 映射应用于多种数据库之中。
-
现实情况下有多种理由需要这么做。例如,
开发、测试和生产环境需要有不同的配置 -
不过要记住:
尽管可以配置多个环境,但每个 SqlSessionFactory 实例只能选择一种环境。根据id属性选择配置
<environments default="dev">
<!--开发环境-->
<environment id="dev">
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<property name="driver" value="${driver}"/>
<!--在xml中,特殊字符需要转义&相当于&-->
<property name="url" value="${url}"/>
<property name="username" value="${name}"/>
<property name="password" value="${password}"/>
</dataSource>
</environment>
<!--测试环境-->
<environment id="test">
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<property name="driver" value="${driver}"/>
<!--在xml中,特殊字符需要转义&相当于&-->
<property name="url" value="${url}"/>
<property name="username" value="${name}"/>
<property name="password" value="${password}"/>
</dataSource>
</environment>
</environments>
transacationManager(事务管理器)
- 在 MyBatis 中有两种类型的事务管理器(也就是 type="[JDBC|MANAGED]")
- Mybatis默认的事务管理器是
JDBC,连接池:POOLED
JDBC
- 这个配置直接使用了 JDBC 的提交和回滚设施,它依赖从数据源获得的连接来管理事务作用域。
MANAGED
- 这个配置几乎没做什么。它从不提交或回滚一个连接,而是让容器来管理事务的整个生命周期(比如 JEE 应用服务器的上下文)。 默认情况下它会关闭连接。
注意
-
如果使用 Spring + MyBatis,则没有必要配置事务管理器,因为 Spring 模块会使用自带的管理器来覆盖前面的配置。
-
这两种事务管理器类型都不需要设置任何属性。它们其实是类型别名,换句话说,你可以用 TransactionFactory 接口实现类的全限定名或类型别名代替它们。
dataSource(数据源)
- dataSource 元素使用标准的 JDBC 数据源接口来配置 JDBC 连接对象的资源。
- 大多数 MyBatis 应用程序会按示例中的例子来配置数据源。虽然数据源配置是可选的,但如果要启用延迟加载特性,就必须配置数据源。
- 有三种内建的数据源类型(也就是 type="[UNPOOLED|POOLED|JNDI]"):
UNPOOLED
- 这个数据源的实现会每次请求时打开和关闭连接。虽然有点慢,但对那些数据库连接可用性要求不高的简单应用程序来说,是一个很好的选择
POOLED
- 这种数据源的实现利用“池”的概念将 JDBC 连接对象组织起来,避免了创建新的连接实例时所必需的初始化和认证时间。 这种处理方式很流行,能使并发 Web 应用快速响应请求。
JNDI
- 这个数据源实现是为了能在如 EJB 或应用服务器这类容器中使用,容器可以集中或在外部配置数据源,然后放置一个 JNDI 上下文的数据源引用。
1.4.5 plugins(插件)
- 使用外部插件可以进一步简化开发
在maven仓库中可以查询
- mybatis-generator-core(自动生成代码)
- mybatis-plus(mybatis增强版)
- 通用mapper
1.4.6 mappers
- 告诉Mybatis去哪找SQL映射语句
- 可以使用相对于类路径的资源引用,或完全限定资源定位符
resource·
<!-- 使用相对于类路径的资源引用 -->
<mappers>
<mapper resource="org/mybatis/builder/AuthorMapper.xml"/>
</mappers>
class
- 注意:接口和他的Mapper配置文件必须同名。
- 接口和他的配置文件必须在同一个包下。
<!-- 使用映射器接口实现类的完全限定类名 -->
<mappers>
<mapper class="org.mybatis.builder.AuthorMapper"/>
</mappers>
package
<!-- 将包内的映射器接口实现全部注册为映射器 -->
<mappers>
<package name="org.mybatis.builder"/>
</mappers>
1.5 作用域和生命周期
- 不同作用域和生命周期类别是至关重要的,因为错误的使用会导致非常严重的并发问题。

SqlSessionFactoryBuilder
- 这个类可以被实例化、使用和丢弃,一旦创建了 SqlSessionFactory,就不再需要它了。
- 因此 SqlSessionFactoryBuilder 实例的最佳作用域是方法作用域(也就是局部方法变量)。
SqlSessionFactory
- SqlSessionFactory相当于数据库连接池, 一旦被创建就应该在应用的运行期间一直存在,没有任何理由丢弃它或重新创建另一个实例。
- 因此 SqlSessionFactory 的最佳作用域是应用作用域。
- 最简单的就是使用单例模式或者静态单例模式。
SqlSession
- 连接到连接池的一个请求
- sqlSession的实例不是线程安全的,因此是不能被共享的,所以它的最佳作用域是请求或者方法作用域,定义在一个方法体中。
- 用完需要赶紧关闭,否则资源被占用

- 这里面的每一个Mapper就代表一个具体的业务
- 一个sqlSession可以有多个Mapper
@Test
public void getLikeUser(){
SqlSession sqlSession = MybatisUtil.getSqlSession();
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
UserMapper mapper1 = sqlSession.getMapper(UserMapper.class);
System.out.println(mapper.hashCode());//1924582348
System.out.println(mapper1.hashCode());//11003494
List<User> users1 = mapper1.findAll();
for (User user : users1) {
System.out.println(user);
}
List<User> users = mapper.getLikeUser("强");
for (User user : users) {
System.out.println(user);
}
sqlSession.close();
}
1.6 属性名和字段名不一致
- 如果数据库中的字段名和实体类中的属性不一致的话,该字段的查询结果会为null
- 实体类中属性名为pwd,数据库中字段为password
User{id=1, name='zhangsan', pwd='null'}
起别名
select id,name,password as pwd from mybatis.user where id = #{id};
resultMap(结果集映射)
- resultMap 元素是 MyBatis 中最重要最强大的元素。
- ResultMap 的设计思想是,对简单的语句做到零配置,对于复杂一点的语句,只需要描述语句之间的关系就行了。
id name password
id name pwd
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="cn.rm.mapper.UserMapper">
<!--i对应需要映射的结果集映射,type对应的实体类-->
<resultMap id="UserMap" type="User">
<!--column对应数据库中的字段,property实体类中的属性-->
<result column="password" property="pwd"/>
</resultMap>
<select id="findAll" resultMap="UserMap">
select * from user
</select>
</mapper>
1.7 日志
1.7.1 日志工厂
-
如果一个数据库操作,出现了异常,需要排错。这时候就需要使用日志了。
-
以前:sout、debug。现在:日志工厂
-
在Mybatis中具体使用哪一个日志实现,在setting标签中设置
-
STDOUT_LOGGING: 标准日志输出
在settings中设置日志模式
<!--使用日志工厂-->
<settings>
<setting name="logImpl" value="STDOUT_LOGGING"/>
</settings>
控制台输出结果
Opening JDBC Connection
Created connection 1843289228.
Setting autocommit to false on JDBC Connection [com.mysql.jdbc.JDBC4Connection@6dde5c8c]
==> Preparing: select * from user where id = ?
==> Parameters: 1(Integer)
<== Columns: id, name, password
<== Row: 1, zhangsan, 123456
<== Total: 1
User{id=1, name='zhangsan', password='123456'}
Resetting autocommit to true on JDBC Connection [com.mysql.jdbc.JDBC4Connection@6dde5c8c]
Closing JDBC Connection [com.mysql.jdbc.JDBC4Connection@6dde5c8c]
Returned connection 1843289228 to pool.
1.8 Log4j
1.8.1 什么是Log4j?
- Log4j是Apache的一个开源项目,通过使用Log4j,我们可以控制日志信息输送的目的地是控制台、文件.GUI组件
- 我们也可以控制每一条日志的输出格式;
- 通过定义每一条日志信息的级别,我们能够更加细致地控制日志的生成过程。
- 通过一个配置文件来灵活地进行配置,而不需要修改应用的代码。
1.8.2 快速使用
- 导包
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.17</version>
</dependency>
- 编写配置文件log4j.properties
#将等级为DEBUG的日志信息输出到console和file这两个目的地,console和file的定义在下面的代码
log4j.rootLogger=DEBUG,console,file
#控制台输出的相关设置
log4j.appender.console = org.apache.log4j.ConsoleAppender
log4j.appender.console.Target = System.out
log4j.appender.console.Threshold=DEBUG
log4j.appender.console.layout = org.apache.log4j.PatternLayout
log4j.appender.console.layout.ConversionPattern=[%c]-%m%n
#文件输出的相关设置
log4j.appender.file = org.apache.log4j.RollingFileAppender
log4j.appender.file.File=./log/rm.log
log4j.appender.file.MaxFileSize=10mb
log4j.appender.file.Threshold=DEBUG
log4j.appender.file.layout=org.apache.log4j.PatternLayout
log4j.appender.file.layout.ConversionPattern=[%p][%d{yy-MM-dd}][%c]%m%n
#日志输出级别
log4j.logger.org.mybatis=DEBUG
log4j.logger.java.sql=DEBUG
log4j.logger.java.sql.Statement=DEBUG
log4j.logger.java.sql.ResultSet=DEBUG
log4j.logger.java.sql.PreparedStatement=DEBUG
- 配置为log4j的日志实现
<settings>
<setting name="logImpl" value="LOG4J"/>
</settings>
- log4j使用结果
[org.apache.ibatis.transaction.jdbc.JdbcTransaction]-Opening JDBC Connection
[org.apache.ibatis.datasource.pooled.PooledDataSource]-Created connection 824208363.
[org.apache.ibatis.transaction.jdbc.JdbcTransaction]-Setting autocommit to false on JDBC Connection [com.mysql.jdbc.JDBC4Connection@31206beb]
[cn.rm.mapper.UserMapper.getUserById]-==> Preparing: select * from user where id = ?
[cn.rm.mapper.UserMapper.getUserById]-==> Parameters: 1(Integer)
[cn.rm.mapper.UserMapper.getUserById]-<== Total: 1
User{id=1, name='zhangsan', password='123456'}
[org.apache.ibatis.transaction.jdbc.JdbcTransaction]-Resetting autocommit to true on JDBC Connection [com.mysql.jdbc.JDBC4Connection@31206beb]
[org.apache.ibatis.transaction.jdbc.JdbcTransaction]-Closing JDBC Connection [com.mysql.jdbc.JDBC4Connection@31206beb]
[org.apache.ibatis.datasource.pooled.PooledDataSource]-Returned connection 824208363 to pool.
1.8.3 自定义使用Log4j
- 在需要使用log4j的类中导入包
import org.apache.log4j.Logger;
- 生成日志对象,加载参数为当前类的class
Logger logger = Logger.getLogger(UserMapperTest.class);
- 使用日志对象
public class UserMapperTest {
Logger logger = Logger.getLogger(UserMapperTest.class);
@Test
public void testLog4j(){
logger.info("info:进入了testLog4j方法");
logger.debug("debug:进入了testLog4j方法");
logger.error("error:进入了testLog4j方法");
}
}
- 查看输出结果
[cn.rm.mapper.UserMapperTest]-info:进入了testLog4j方法
[cn.rm.mapper.UserMapperTest]-debug:进入了testLog4j方法
[cn.rm.mapper.UserMapperTest]-error:进入了testLog4j方法
- 生成的日志文件中的结果

1.9 分页
- 分页为了减少数据的处理量
1.9.1 使用limit分页
select * from user limit startIndex,PageSize;
select * from user limit 3; #[0,3)
- 编写Mapper
//实现分页查询功能
List<User> selectUserByLimit(Map<String,Integer> map);
- 编写对应的xml文件
<!--分页查询功能-->
<select id="selectUserByLimit" resultType="user" parameterType="map">
select * from mybatis.user limit #{startIndex},#{pageSize}
</select>
- 测试代码
@Test
public void selectUserByLimit(){
SqlSession sqlSession = MybatisUtil.getSqlSession();
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
HashMap<String, Integer> map = new HashMap<String, Integer>();
map.put("startIndex",1);
map.put("pageSize",3);
List<User> users = mapper.selectUserByLimit(map);
for (User user : users) {
System.out.println(user);
}
sqlSession.close();
}
- 输出结果
[cn.rm.mapper.UserMapper.selectUserByLimit]-==> Preparing: select * from mybatis.user limit ?,?
[cn.rm.mapper.UserMapper.selectUserByLimit]-==> Parameters: 1(Integer), 3(Integer)
[cn.rm.mapper.UserMapper.selectUserByLimit]-<== Total: 3
User{id=2, name='lisi', password='123456'}
User{id=3, name='诸葛青', password='111'}
User{id=4, name='麻子', password='123456'}
1.9.2 RowBounds(不建议使用)
- 编写Mapper
//实现分页查询功能
List<User> getUserByRowBounds();
- 编写对应的xml文件
<!--java代码实现分页查询-->
<select id="getUserByRowBounds" resultType="user">
select * from user;
</select>
- 测试代码
@Test
public void selectByRowBounds(){
//通过java代码实现分页查询
SqlSession sqlSession = MybatisUtil.getSqlSession();
RowBounds rowBounds = new RowBounds(1, 2);
List<User> userList = sqlSession.selectList("cn.rm.dao.UserMapper.getUserByRowBounds", null, rowBounds);
for (User user : userList) {
System.out.println(user);
}
sqlSession.close();
}
1.9.3 pageHelper插件

1.10 使用注解开发
1.10.1 面向接口编程
-
大家之前都学过面向对象编程,也学习过接口,但在真正的开发中,很多时候我们会选择面向接口编程
-
根本原因 : 解耦 , 可拓展 , 提高复用 , 分层开发中 , 上层不用管具体的实现 , 大家都遵守共同的标准 , 使得开发变得容易 , 规范性更好
-
在一个面向对象的系统中,系统的各种功能是由许许多多的不同对象协作完成的。在这种情况下,各个对象内部是如何实现自己的,对系统设计人员来讲就不那么重要了;
-
而各个对象之间的协作关系则成为系统设计的关键。小到不同类之间的通信,大到各模块之间的交互,在系统设计之初都是要着重考虑的,这也是系统设计的主要工作内容。面向接口编程就是指按照这种思想来编程。
关于接口的理解
-
接口从更深层次的理解,应是定义(规范,约束)与实现(名实分离的原则)的分离。
-
接口的本身反映了系统设计人员对系统的抽象理解。
接口应有两类: -
第一类是对一个个体的抽象,它可对应为一个抽象体(abstract class);
-
第二类是对一个个体某一方面的抽象,即形成一个抽象面(interface);
-
一个体有可能有多个抽象面。抽象体与抽象面是有区别的。
三个面向区别
- 面向对象是指,我们考虑问题时,以对象为单位,考虑它的属性及方法 .
- 面向过程是指,我们考虑问题时,以一个具体的流程(事务过程)为单位,考虑它的实现
- 接口设计与非接口设计是针对复用技术而言的,与面向对象(过程)不是一个问题.更多的体现就是对系统整体的架构
1.10.2 使用注解开发
- 注解在接口上实现
@Select("select * from user where id = #{id}")
//根据id查询用户
User getUserById(int id);
- 在mybatis-config中绑定接口
<!--绑定接口-->
<mappers>
<mapper class="cn.rm.dao.UserMapper"/>
</mappers>
- 测试,查看结果
public class UserMapperTest {
@Test
public void getUsers(){
SqlSession sqlSession = MybatisUtil.getSqlSession();
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
List<User> users = mapper.getUsers();
for (User user : users) {
System.out.println(user);
}
sqlSession.close();
}
}
- 使用debug分析

注解本质使用反射机制,底层使用动态代理
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
- 该语句可以获得UserMapper的所有东西
Mybatis详细的执行流程

1.10.3 使用注解完成CRUD
只需要修改UserMapper和测试类即可
- UserMapper
public interface UserMapper {
//查询所有
@Select("select * from user")
List<User> getUsers();
//增加一个用户
@Insert("insert into user values(#{id},#{name},#{password})")
int addUser(User user);
//根据id删除一个用户
@Delete("delete from user where id =#{id}")
int deleteUser(int id);
//根据id更新一个用户的信息
@Update("update user set password =#{password} where id=#{id}")
int updateUser(Map<String,Object> map);
}
- Test
public class UserMapperTest {
@Test
public void getUsers(){
SqlSession sqlSession = MybatisUtil.getSqlSession();
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
List<User> users = mapper.getUsers();
for (User user : users) {
System.out.println(user);
}
sqlSession.close();
}
@Test
public void addUser(){
SqlSession sqlSession = MybatisUtil.getSqlSession();
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
int i = mapper.addUser(new User(10, "嘤嘤嘤", "5555555"));
if(i>0){
System.out.println("插入成功");
}
sqlSession.commit();
sqlSession.close();
}
@Test
public void deleteUser(){
SqlSession sqlSession = MybatisUtil.getSqlSession();
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
int i = mapper.deleteUser(10);
if(i>0){
System.out.println("删除成功");
}
sqlSession.commit();
sqlSession.close();
}
@Test
public void updateUser(){
SqlSession sqlSession = MybatisUtil.getSqlSession();
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
HashMap map = new HashMap();
map.put("password","8888888");
map.put("id",9);
int i = mapper.updateUser(map);
if(i>0){
System.out.println("修改成功");
}
sqlSession.commit();
sqlSession.close();
}
}
#{}和${}的区别
{}带预编译功能,预防sql注入
${}不带预编译
#{}
//根据id查用户
@Select("select * from user where id=#{id}")
User getUserById(int id);
结果
==> Preparing: select * from user where id=?
==> Parameters: 5(Integer)
${}
//根据id查用户
@Select("select * from user where id=${id}")
User getUserById(int id);
结果
==> Preparing: select * from user where id=5
==> Parameters:
@param()
- 基本类型的参数或者String类型,需要加上
- 引用类型不用加
- 如果参数只有一个基本类型的话,可以忽略
- 在SQL中引用的就是该注解中的属性名
//根据id查用户,传入的参数就是@Param中的值
@Select("select * from user where id=${idddddd}")
User getUserById(@Param("idddddd") int id);
设置自动提交事务
- 在创建sqlSession的时候就默认自动提交事务
- 开启Sesson的时候传递一个true参数即可
//获取sqlSession
public static SqlSession getSqlSession(){
return sqlSessionFactory.openSession(true);
}

1.11 Lombok
- 一款注解插件,只需在实体类上面添加该注解,不用再写getset方法
安装插件:

包含的注解:
@Getter and @Setter
@FieldNameConstants
@ToString
@EqualsAndHashCode
@AllArgsConstructor, @RequiredArgsConstructor and @NoArgsConstructor
@Log, @Log4j, @Log4j2, @Slf4j, @XSlf4j, @CommonsLog, @JBossLog, @Flogger, @CustomLog
@Data
@Builder
@SuperBuilder
@Singular
@Delegate
@Value
@Accessors
@Wither
@With
@SneakyThrows
@val
@var
experimental @var
@UtilityClass
使用步骤:
1. 在IDEA中安装Lombok插件
2. 在项目中导入lombokjar包
3. 在实体类中加注解
- 添加lombok依赖
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.12</version>
<scope>provided</scope>
</dependency>
说明
@Data:无参构造、get、set、toString、hashcode、equals
@AllArgsConstructor:全参构造,加入该注解后,@Data生成的无参构造就会消失
@NoArgsConstructor:无参构造
1.12 多对一的处理
- 多个学生,对应一个老师
- 对于学生这边来说:关联...多个学生关联一个老师(association)【多对一】
- 对于老师而言:集合...一个老师拥有多个学生(collection)【一对多】
teacher表
CREATE TABLE `teacher` (
`id` INT(10) NOT NULL,
`name` VARCHAR(30) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=INNODB DEFAULT CHARSET=utf8
student表
CREATE TABLE `student` (
`id` INT(10) NOT NULL AUTO_INCREMENT,
`NAME` VARCHAR(30) DEFAULT NULL,
`tid` INT(10) DEFAULT NULL,
PRIMARY KEY (`id`),
KEY `fktid` (`tid`),
CONSTRAINT `fktid` FOREIGN KEY (`tid`) REFERENCES `teacher` (`id`)
) ENGINE=INNODB AUTO_INCREMENT=6 DEFAULT CHARSET=utf8
- 通过物理外键实现多对一

1.12.1 多对一处理
- 导入项目需要的依赖
mysql-connector-java
junit
mybatis
lombok
-
编写核心配置文件mybatis-config.xml
-
编写工具类MybatisUtil
-
编写实体类Student和Teacher
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Student {
private int id;
private String name;
//学生需要关联一个老师
private Teacher teacher;
}
- 编写对应的Mapper
- 将mapper注册到mybatis-config.xml中
<!--注册mappers-->
<mappers>
<mapper class="cn.rm.dao.TeacherMapper"/>
<mapper class="cn.rm.dao.StudentMapper"/>
</mappers>
- 测试
查询所有的学生信息,以及对应的老师的信息
select s.id,s.NAME,t.name from student s, teacher t where s.tid = t.id

按照查询嵌套处理
StudentMapper
public interface StudentMapper {
List<Student> getStudents();
}
StudentMapper.xml
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<!--namespace绑定接口-->
<mapper namespace="cn.rm.dao.StudentMapper">
<!--查询所有的学生信息-->
<select id="getStudents" resultMap="studentTeacher">
select * from student
</select>
<!--resultMap-->
<resultMap id="studentTeacher" type="student">
<result column="id" property="id"/>
<result column="name" property="name"/>
<!--实体类中是对象,数据库中的tid字段,要使这两个对应上-->
<!--复杂的属性,需要单独处理,实体类中的对象属性:association 集合:collection-->
<association property="teacher" column="tid" javaType="teacher" select="getTeacher"/>
</resultMap>
<select id="getTeacher" resultType="teacher">
select * from mybatis.teacher where id=#{id}
</select>
</mapper>
测试结果
==> Preparing: select * from student
==> Parameters:
<== Columns: id, NAME, tid
<== Row: 1, 小明, 1
====> Preparing: select * from mybatis.teacher where id=?
====> Parameters: 1(Integer)
<==== Columns: id, name
<==== Row: 1, 王老师
<==== Total: 1
<== Row: 2, 小红, 1
<== Row: 3, 小蓝, 1
<== Row: 4, 小王, 1
<== Row: 5, 小李, 1
<== Total: 5
Student(id=1, name=小明, teacher=Teacher(id=1, name=王老师))
Student(id=2, name=小红, teacher=Teacher(id=1, name=王老师))
Student(id=3, name=小蓝, teacher=Teacher(id=1, name=王老师))
Student(id=4, name=小王, teacher=Teacher(id=1, name=王老师))
Student(id=5, name=小李, teacher=Teacher(id=1, name=王老师))
按照结果嵌套处理
StudentMapper.xml
<select id="getStudents2" resultMap="studentTeacher2">
select s.id sid,s.name sname,t.name tname
from student s, teacher t
where s.tid = t.id
</select>
<resultMap id="studentTeacher2" type="student">
<result property="id" column="sid"/>
<result property="name" column="sname"/>
<association property="teacher" javaType="teacher">
<!--teacher表中的对应字段-->
<result property="name" column="tname"/>
</association>
</resultMap>
测试结果
==> Preparing: select s.id sid,s.name sname,t.name tname from student s, teacher t where s.tid = t.id
==> Parameters:
<== Columns: sid, sname, tname
<== Row: 1, 小明, 王老师
<== Row: 2, 小红, 王老师
<== Row: 3, 小蓝, 王老师
<== Row: 4, 小王, 王老师
<== Row: 5, 小李, 王老师
<== Total: 5
Student(id=1, name=小明, teacher=Teacher(id=0, name=王老师))
Student(id=2, name=小红, teacher=Teacher(id=0, name=王老师))
Student(id=3, name=小蓝, teacher=Teacher(id=0, name=王老师))
Student(id=4, name=小王, teacher=Teacher(id=0, name=王老师))
Student(id=5, name=小李, teacher=Teacher(id=0, name=王老师))
回顾Mysql多对一查询方式
- 子查询【select id,NAME,tid from student where tid=(select id from teacher where student.tid=teacher.id)】
- 联表查询【select s.id,s.NAME,t.name from student s, teacher t where s.tid = t.id;】
1.12.2 一对多处理
- 一个老师拥有多个学生
- 对老师而言,就是一对多的关系
- 环境搭建
实体类
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Teacher {
private int id;
private String name;
//一个老师拥有多个学生
private List<Student> students;
}
public class Student {
private int id;
private String name;
private int tid;
}
1.12.3 按照结果嵌套处理
<!--1.按结果嵌套查询-->
<select id="getTeacher" resultMap="TeacherStudent">
SELECT t.id tid, t.name tname,s.id sid ,s.name sname
FROM student s,teacher t
where s.tid=t.id
and t.id =#{id}
</select>
<resultMap id="TeacherStudent" type="teacher">
<!--将属性和字段名进行匹配-->
<result property="id" column="tid"/>
<result property="name" column="tname"/>
<!--对象用association,集合用collection-->
<!--javaType="" 指定属性的类型
ofType="" 指定集合中的泛型信息
-->
<collection property="students" ofType="student">
<result property="id" column="sid"/>
<result property="name" column="sname"/>
<result property="tid" column="tid"/>
</collection>
</resultMap>
测试结果
/*
==> Preparing: SELECT t.id tid, t.name tname,s.id sid ,s.name sname FROM student s,teacher t where s.tid=t.id and t.id =?
==> Parameters: 1(Integer)
<== Columns: tid, tname, sid, sname
<== Row: 1, 王老师, 1, 小明
<== Row: 1, 王老师, 2, 小红
<== Row: 1, 王老师, 3, 小蓝
<== Row: 1, 王老师, 4, 小王
<== Row: 1, 王老师, 5, 小李
<== Total: 5
Teacher(id=1,
name=王老师,
students=[Student(id=1, name=小明, tid=1),
Student(id=2, name=小红, tid=1),
Student(id=3, name=小蓝, tid=1),
Student(id=4, name=小王, tid=1),
Student(id=5, name=小李, tid=1)])
*/
1.12.4 小结
-
关联:association【多对一】
-
集合:collection 【一对多】
-
JavaType & ofType
注意点
- 保证SQL的可读性,尽量保证通俗易懂
- 注意一对多和多对一中,属性名和字段的问题
- 如果问题不好排查,建议使用Log4j日志
面试高频
- Mysql引擎
- InnoDB底层原理
- 索引
- 索引优化
1.13 动态sql
概念:
- 动态sql就是根据不同的需求生成不同的sql语句
if
choose(when,otherwise)
trim(where,set)
foreach
1.13.1 搭建环境
- mybatis-config.xml
- 工具类MybatisUtil、IDUtil
- 实体类Blog
- dao接口BlogMapper
- 对应的BlogMapper.xml
- 测试
IDUtil工具类
- 生成一个随机的ID
public class IDUtil {
public static String getID(){
//返回一个随机生成的id
String s = UUID.randomUUID().toString();
//将-替换为空(7949cc75-b278-4db7-93f1-5b4e6eb6cf25)
return s.replace("-","");
}
@Test
public void test(){
System.out.println(IDUtil.getID());
}
//测试结果 88c4c6ec951d46eb809984cb6ee2758c
}
数据库字段和实体类属性不一致
- 在setting中将自动驼峰转换打开
<!--使用日志工厂,开启自动驼峰转换-->
<settings>
<setting name="logImpl" value="STDOUT_LOGGING"/>
<setting name="mapUnderscoreToCamelCase" value="true"/>
</settings>
1.13.2 if标签
- sql语句后面添加where1=1是为了第一个条件不成立的时候拼接第二个and语句
<!--如果不传递参数就查询所有,传递参数就根据参数进行sql的拼接-->
<select id="selectBlogIf" resultType="blog" parameterType="map">
select * from mybatis.blog where 1=1
<if test="title != null">
and title = #{title}
</if>
<if test="author != null">
and author=#{author}
</if>
</select>
- 测试
@Test
public void selectBlogIf() {
BlogMapper mapper = MybatisUtil.getSqlSession().getMapper(BlogMapper.class);
HashMap map = new HashMap();
map.put("title", "微服务_你好");
map.put("author", "rm");
List<Blog> blogs = mapper.selectBlogIf(map);
for (Blog blog : blogs) {
System.out.println(blog);
}
}
- 结果
==> Preparing: select * from mybatis.blog where 1=1 and title = ? and author=?
==> Parameters: 微服务_你好(String), rm(String)
<== Columns: id, title, author, create_time, views
<== Row: 8de626bb226d48cbb4b492578de26d7e, 微服务_你好, rm, 2020-08-25 22:28:00.0, 600
<== Total: 1
Blog(id=8de626bb226d48cbb4b492578de26d7e, title=微服务_你好, author=rm, createTime=Tue Aug 25 22:28:00 CST 2020, views=600)
1.13.3 choose、when、otherwise标签
- 有时候,我们不想使用所有的条件,而只是想从多个条件中选择一个使用。针对这种情况,MyBatis 提供了 choose 元素,它有点像 Java 中的 switch 语句。
<select id="selectBlogChoose" resultType="blog" parameterType="map">
select * from mybatis.blog
<where>
<choose>
<when test="title !=null">
and title = #{title}
</when>
<when test="author != null">
and author = #{author}
</when>
<otherwise>
</otherwise>
</choose>
</where>
</select>
-
where 元素只会在子元素返回任何内容的情况下才插入 “WHERE” 子句。而且,若子句的开头为 “AND” 或 “OR”,where 元素也会将它们去除。
-
如果两个条件都成立,只会拼接上面的语句
@Test
public void selectBlogChoose() {
BlogMapper mapper = MybatisUtil.getSqlSession().getMapper(BlogMapper.class);
HashMap map = new HashMap();
map.put("title", "微服务_你好");
map.put("author", "rm");
List<Blog> blogs = mapper.selectBlogChoose(map);
for (Blog blog : blogs) {
System.out.println(blog);
}
}
结果
==> Preparing: select * from mybatis.blog WHERE title = ?
==> Parameters: 微服务_你好(String)
<== Columns: id, title, author, create_time, views
<== Row: 8de626bb226d48cbb4b492578de26d7e, 微服务_你好, rm, 2020-08-25 22:28:00.0, 600
<== Row: a3f0cb676ad6491c89a01789834d3417, 微服务_你好, rm2, 2020-08-30 21:56:11.0, 500
<== Total: 2
Blog(id=8de626bb226d48cbb4b492578de26d7e, title=微服务_你好, author=rm, createTime=Tue Aug 25 22:28:00 CST 2020, views=600)
Blog(id=a3f0cb676ad6491c89a01789834d3417, title=微服务_你好, author=rm2, createTime=Sun Aug 30 21:56:11 CST 2020, views=500)
1.13.4 trim、where、set标签
set
- set 元素会动态地在行首插入 SET 关键字,并会删掉额外的逗号(这些逗号是在使用条件语句给列赋值时引入的)。
<update id="updateBlog" parameterType="map">
update mybatis.blog
<set >
<if test="title != null">
title=#{title},
</if>
<if test="author != null">
author=#{author},
</if>
<if test="create_time != null">
create_time=#{createTime},
</if>
<if test="views != null">
views=#{views}
</if>
</set>
where id = #{id}
</update>
- 测试
@Test
public void updateBlog() {
BlogMapper mapper = MybatisUtil.getSqlSession().getMapper(BlogMapper.class);
HashMap map = new HashMap();
map.put("title", "微服务我我我我我我你好");
map.put("author", "xxxxxx");
map.put("id","a3f0cb676ad6491c89a01789834d3417");
int i = mapper.updateBlog(map);
if (i>0){
System.out.println("修改成功");
}
}
结果
==> Preparing: update mybatis.blog SET title=?, author=? where id = ?
==> Parameters: 微服务我我我我我我你好(String), xxxxxx(String), a3f0cb676ad6491c89a01789834d3417(String)
<== Updates: 1
修改成功
所谓的动态sql,本质还是sql语句,只是我们可以在sql层面,去执行一个逻辑代码
1.13.5 sql标签
有的时候,我们需要将一些功能的部分抽取出来,方便复用
<sql id="title-author">
<if test="title != null">
and title = #{title}
</if>
<if test="author != null">
and author=#{author}
</if>
</sql>
在需要引用该片段的地方使用include引用
<select id="selectBlogIf" resultType="blog" parameterType="map">
select * from mybatis.blog
<where>
<include refid="title-author"/>
<where/>
</select>
注意事项
- 最好基于单表来定义SQL片段
- 不要存在where标签
1.13.6 foreach标签
- 动态 SQL 的另一个常见使用场景是对集合进行遍历(尤其是在构建 IN 条件语句的时候)。

- 想要获取id为1和3的信息
- 使用sql语句查询
select * from mybatis.blog where (id=1 or id=3)
通过动态sql实现
<!--通过map传递一个集合select * from mybatis.blog where (id=1 or id=3)-->
<select id="selectBlogForeach" resultType="blog" parameterType="map">
select * from mybatis.blog
<where>
<foreach collection="ids" item="id" open="(" separator="or" close=")">
id = #{id}
</foreach>
</where>
</select>
注意事项
- foreach标签中的ids是map中传递的ids的值
- item表示的是从ids集合中取得的每一个元素,用做下面的为id赋值
测试
@Test
public void selectBlogForeach(){
BlogMapper mapper = MybatisUtil.getSqlSession().getMapper(BlogMapper.class);
HashMap map = new HashMap();
ArrayList<Integer> ids = new ArrayList<Integer>();
ids.add(1);
ids.add(3);
System.out.println(ids);
map.put("ids",ids);
List<Blog> blogs = mapper.selectBlogForeach(map);
for (Blog blog : blogs) {
System.out.println(blog);
}
}
1.14 缓存
每次仅限查询都会连接数据库,消耗资源
一次查询的结果,将它暂时存放到一个可以直接取到的地方(内存)
再次查询该数据的时候就可以直接到内存中取,而不用再连接数据库了
1.14.1 什么是缓存?
- 存在内存中的临时数据。
- 将用户经常查询的数据放在缓存(内存)中,用户去查询数据就不用从磁盘上(关系型数据库数据文件)查询,从缓存中查询,从而提高查询效率,解决了高并发系统的性能问题。
1.14.2 为什么使用缓存?
- 减少和数据库的交互次数,减少系统开销,提高系统效率。
1.14.3 什么样的数据能使用缓存?
- 经常查询并且不经常改变的数据。【可以使用缓存】
1.15 Mybatis缓存
MyBatis包含一个非常强大的查询缓存特性,它可以非常方便地定制和配置缓存。缓存可以极大的提升查询效率。
MyBatis系统中默认定义了两级缓存:一级缓存和二级缓存
- 默认情况下,只有一级缓存开启。(SqlSession级别的缓存,也称为本地缓存)
- 二级缓存需要手动开启和配置,他是基于namespace级别的缓存。
- 为了提高扩展性,MyBatis定义了缓存接口Cache。我们可以通过实现Cache接口来自定义二级缓存
import org.apache.ibatis.cache.Cache;
public interface Cache {
String getId();
void putObject(Object var1, Object var2);
Object getObject(Object var1);
Object removeObject(Object var1);
void clear();
int getSize();
default ReadWriteLock getReadWriteLock() {
return null;
}
}
1.15.1 一级缓存
- 一级缓存也叫本地缓存:
- 与数据库同一次会话期间查询到的数据会放在本地缓存中。
- 以后如果需要获取相同的数据,直接从缓存中拿,没必须再去查询数据库;
测试步骤
- 开启日志
<settings>
<setting name="logImpl" value="STDOUT_LOGGING"/>
</settings>
- 测试在一个session中查询两次相同记录
@Test
public void getUserById(){
SqlSession sqlSession = MybatisUtil.getSqlSession();
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
User user = mapper.getUserById(1);
System.out.println(user);
System.out.println("=============");
User user1 = mapper.getUserById(1);
System.out.println(user1);
System.out.println(user==user1);
sqlSession.close();
}
测试结果
Opening JDBC Connection
Created connection 22805895.
==> Preparing: select * from mybatis.user where id =?
==> Parameters: 1(Integer)
<== Columns: id, name, password
<== Row: 1, zhangsan, 123456
<== Total: 1
User(id=1, name=zhangsan, password=123456)
=============
User(id=1, name=zhangsan, password=123456)
true
Closing JDBC Connection [com.mysql.jdbc.JDBC4Connection@15bfd87]
Returned connection 22805895 to pool.
缓存失效的情况
- 查询不同的内容
- 增删改操作,可能会改变原来的数据,会刷新缓存
public class MyTest {
@Test
public void getUserById(){
SqlSession sqlSession = MybatisUtil.getSqlSession();
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
User user = mapper.getUserById(1);
System.out.println(user);
HashMap map = new HashMap();
map.put("name","hhhhh");
map.put("id",2);
mapper.updateUser(map);
System.out.println("=============");
User user1 = mapper.getUserById(1);
System.out.println(user1);
System.out.println(user==user1);
sqlSession.close();
}
}
结果

- 查询不同的Mapper.xml
- 手动清除缓存
public class MyTest {
@Test
public void getUserById(){
SqlSession sqlSession = MybatisUtil.getSqlSession();
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
User user = mapper.getUserById(1);
sqlSession.clearCache();
System.out.println("=============");
User user1 = mapper.getUserById(1);
sqlSession.close();
}
}
结果

小结
- 一级缓存默认开启,只在一次sqlSession中有效,也就是拿到连接到关闭连接这个区间
- 一级缓存就是一个Map

1.15.2 二级缓存
- 二级缓存也叫全局缓存,一级缓存作用域太低了,所以诞生了二级缓存
- 基于namespace级别的缓存,一个名称空间,对应一个二级缓存;
- 工作机制
- 一个会话查询一条数据,这个数据就会被放在当前会话的一级缓存中;
- 如果当前会话关闭了,这个会话对应的一级缓存就没了;但是我们想要的是,会话关闭了,一级缓存中的数据被保存到二级缓存中。
- 新的会话查询信息,就可以从二级缓存中获取内容;
- 不同的mapper查出的数据会放在自己对应的缓存(map)中;
步骤
- 开启全局缓存
<!--显式的开启全局缓存-->
<settings>
<setting name="cacheEnabled" value="true"/>
</settings>
- 在需要开启缓存的mapper.xml中开启二级缓存(可以自定义一些参数)
<!--在当前mapper.xml中使用二级缓存-->
<cache
eviction="FIFO"
flushInterval="60000"
size="512"
readOnly="true"/>
测试
public class MyTest {
@Test
public void getUserById(){
SqlSession sqlSession = MybatisUtil.getSqlSession();
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
User user = mapper.getUserById(1);
sqlSession.close();
//一级缓存关闭后,将数据交给二级缓存
SqlSession sqlSession2 = MybatisUtil.getSqlSession();
UserMapper mapper2 = sqlSession2.getMapper(UserMapper.class);
User user2 = mapper2.getUserById(1);
System.out.println(user==user2);
sqlSession2.close();
}
}
测试结果
- 二级缓存生效,只查询一次

小结 - 只要开启了二级缓存,在同一个Mapper下就有效
- 所有的数据都会先放在一级缓存中
- 只有当会话提交或者关闭的时候,数据才会从一级缓存提取到二级缓存中。
1.15.3 缓存原理

MyBatis-Generator
MyBatis Generator 是 MyBatis 官方提供的代码生成器插件,可以用于 MyBatis 和 iBatis 框架的代码生成,支持所有版本的 MyBatis 框架以及 2.2.0 版本及以上的 iBatis 框架。
- 在进行功能开发时,一张表我们需要编写实体类、DAO 接口类以及 Mapper 文件,这些是必不可少的文件,如果表的数量较大,我们就需要重复的去创建实体类、Mapper 文件以及 DAO 类并且需要配置它们之间的依赖关系,这无疑是一个很麻烦的事情。
- MyBatis Generator 插件就可以帮助我们去完成这些开发步骤,使用该插件可以帮助开发者自动去创建实体类、Mapper 文件以及 DAO 类并且配置好它们之间的依赖关系,我们可以直接在项目中使用这些代码,后续只需要关注业务方法的开发即可。
2.1 什么是MyBatis-Generator
通过介绍我们可以将 MyBatis-Generator 简单的理解为一个 MyBatis 框架的代码生成器,至于我推荐大家使用它的原因,理由整理如下:
- 减少重复工作
- 减少人为操作带来的错误
- 提升开发效率
- 使用灵活
MyBatis 属于半自动 ORM 框架,在使用这个框架中,工作量最大的就是书写 Mapper 及相关映射文件,同时需要配置其依赖关系,由于手动书写很容易出错,我们可以利用 MyBatis-Generator 来帮我们自动生成文件。
其次,一个项目通常有很多张表,比如接下来要开发的 my_blog 项目,所有的实体类、SQL 语句都要手动编写,这是一个比较繁琐的过程,如果有这么一个插件能够适当的减少我们的一些工作量,自动将这些代码生成到对应的项目目录中,将是一件十分幸福的事情,当然,该插件生成的代码都是常用的一些增删改查代码,如果有其他功能或方法依然需要自己去编写代码,它只是一个提升效率的工具,给予开发者一定程度的帮助。
2.2 MyBatis-Generator 整合
2.2.1 添加依赖
- 如果要使用该插件,首先我们需要将其依赖配置增加到 pom.xml 文件中,增加的配置文件如下,将该部分配置放到原 pom.xml 文件的 plugins 节点下即可:
<plugin>
<groupId>org.mybatis.generator</groupId>
<artifactId>mybatis-generator-maven-plugin</artifactId>
<version>1.3.5</version>
<dependencies>
<dependency>
<groupId> mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version> 5.1.39</version>
</dependency>
<dependency>
<groupId>org.mybatis.generator</groupId>
<artifactId>mybatis-generator-core</artifactId>
<version>1.3.5</version>
</dependency>
</dependencies>
<executions>
<execution>
<id>Generate MyBatis Artifacts</id>
<phase>package</phase>
<goals>
<goal>generate</goal>
</goals>
</execution>
</executions>
<configuration>
<verbose>true</verbose>
<!-- 是否覆盖 -->
<overwrite>true</overwrite>
<!-- MybatisGenerator的配置文件位置 -->
<configurationFile>src/main/resources/mybatisGeneratorConfig.xml</configurationFile>
</configuration>
</plugin>
2.2.2新增 MyBatis-Generator 的配置文件
- 在添加插件依赖到 pom.xml 文件中时,我们定义了 Mybatis Generator 的配置文件位置为 src/main/resources/mybatisGeneratorConfig.xml,该文件即为代码生成器插件最重要的配置文件,后续生成代码的规则、数据库连接信息、代码生成后的存放目录等等配置都需要在该配置文件中定义,配置文件内容及相关注释如下:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE generatorConfiguration
PUBLIC "-//mybatis.org//DTD MyBatis Generator Configuration 1.0//EN"
"http://mybatis.org/dtd/mybatis-generator-config_1_0.dtd">
<generatorConfiguration>
<context id="my-blog-generator-config" targetRuntime="MyBatis3">
<!-- 生成的Java文件的编码 -->
<property name="javaFileEncoding" value="utf-8"/>
<!-- 格式化java代码 -->
<property name="javaFormatter" value="org.mybatis.generator.api.dom.DefaultJavaFormatter"/>
<!-- 格式化XML代码 -->
<property name="xmlFormatter" value="org.mybatis.generator.api.dom.DefaultXmlFormatter"/>
<plugin type="org.mybatis.generator.plugins.ToStringPlugin"/>
<!--创建Java类时对注释进行控制-->
<commentGenerator>
<property name="suppressDate" value="true"/>
<!-- 是否去除自动生成的注释 true:是 : false:否 -->
<property name="suppressAllComments" value="true"/>
</commentGenerator>
<!--数据库地址及登陆账号密码 改成你自己的配置-->
<jdbcConnection
driverClass="com.mysql.jdbc.Driver"
connectionURL="jdbc:mysql://localhost:3306/my_blog_db"
userId="root"
password="root">
</jdbcConnection>
<javaTypeResolver>
<property name="forceBigDecimals" value="false"/>
</javaTypeResolver>
<!--生成实体类设置-->
<javaModelGenerator targetPackage="com.rm.pojo" targetProject="src/main/java">
<property name="enableSubPackages" value="true"/>
<property name="trimStrings" value="true"/>
</javaModelGenerator>
<!--生成Mapper文件设置-->
<sqlMapGenerator targetPackage="mapper" targetProject="src/main/resources">
<property name="enableSubPackages" value="true"/>
</sqlMapGenerator>
<!--生成Dao类设置-->
<javaClientGenerator type="XMLMAPPER" targetPackage="com.rm.dao"
targetProject="src/main/java">
<property name="enableSubPackages" value="true"/>
</javaClientGenerator>
<!--需要自动生成代码的表及对应的类名设置-->
<table tableName="generator_test" domainObjectName="GeneratorTest"
enableCountByExample="false"
enableUpdateByExample="false"
enableDeleteByExample="false" enableSelectByExample="false" selectByExampleQueryId="false">
</table>
</context>
</generatorConfiguration>
需要自动生成代码的表及对应类名的配置是写在 table 标签中的,如上所示,generator_test 表对应的实体类可以配置为 GeneratorTest,这些是开发者自定义的,也可以改成其他合适的类名。 如果有多张表同时生成,增加多个 table 标签配置即可。
2.2.3 生成代码

2.2.4 将 DAO 接口注册至 IOC 容器
-
接下来是最后一个步骤,在代码生成后,我们需要在 DAO 接口类上增加 @Mapper 注解,将其注册到 Spring 的 IOC 容器中以供其他类调用,在生成的文件中默认是没有这个注解的,或者是在主类中添加 @MapperScan 注解将相应包下的所有 Mapper 接口扫描到容器中。
-
需要导入springboot整合mybatis依赖
@SpringBootApplication
@MapperScan("com.rm.dao")
public class GeneratorApplication {
public static void main(String[] args) {
SpringApplication.run(GeneratorApplication.class, args);
}
}
2.3总结
至此,MyBatis-Generator 插件整合完成,也生成了对应的代码,接下来在项目开发时,我们都会使用它来生成我们 DAO 层的代码,之后再根据功能对 Mapper 文件进行修改,使用该插件时的注意事项如下:
由于数据库连接信息是单独定义在配置文件中的,所以在使用时一定要仔细检查,确保数据库连接正常。
在生成代码后,建议将 table 标签注释掉,不然在打包或者生成其他表的代码时会将原先已经生成的代码覆盖,可能会造成一些小麻烦。
代码生成后将 DAO 接口注册至 IOC 容器以供业务方法调用。

浙公网安备 33010602011771号