Mybatis 笔记

本文涉及的产品
云数据库 RDS MySQL,集群系列 2核4GB
推荐场景:
搭建个人博客
RDS MySQL Serverless 基础系列,0.5-2RCU 50GB
RDS MySQL Serverless 高可用系列,价值2615元额度,1个月
简介: Mybatis 笔记

ea26a5aba8acf1c51d5affb078d5b556.png

简介

MyBatis 是一款优秀的持久层框架,它支持自定义 SQL、存储过程以及高级映射。MyBatis 免除了几乎所有的 JDBC 代码以及设置参数和获取结果集的工作。MyBatis 可以通过简单的 XML 或注解来配置和映射原始类型、接口和 Java POJO(Plain Old Java Objects,普通老式 Java 对象)为数据库中的记录。


简单程序样例

  1. 创建 mybatis 数据库,及 user 表
CREATE DATABASE `mybatis`;USE `mybatis`;DROPTABLE IF EXISTS `user`;CREATETABLE `user` (    `id` int(11)NOTNULL,    `name` varchar(30) DEFAULT 'NULL',    `password` varchar(30) DEFAULT 'NULL',    PRIMARY KEY (`id`)) ENGINE=InnoDB DEFAULT CHARSET=utf8;insertinto `user`(`id`,`name`,`password`)values(1,'admin','admin'),(2,'test','test');
  1. IDEA 创建一个 Maven 项目aa58b83c64c6b02f7d7e9ed67a82eac6.png
  2. pom.xml 导入相关依赖
<dependency><groupId>org.mybatis</groupId><artifactId>mybatis</artifactId><version>3.5.6</version></dependency><dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId><version>5.1.47</version></dependency>
  1. 创建实体类
importlombok.AllArgsConstructor;
importlombok.Data;
@Data@AllArgsConstructorpublicclassUser {
privateintid;
privateStringname;
privateStringpassword;
}
  1. 编写 Mapper 接口类
importpojo.User;
importjava.util.List;
importjava.util.Map;
publicinterfaceUserMapper {
intaddUser(Useruser);
intdeleteUserById(intid);
intupdateUserById(Useruser);
List<User>selectUser();
UserselectUserByNameAndPassword(Map<String, Object>map);
}
  1. 编写 Mapper.xml 配置文件
<?xmlversion="1.0" encoding="UTF-8" ?><!DOCTYPE mapperPUBLIC "-//mybatis.org//DTD Mapper 3.0//EN""http://mybatis.org/dtd/mybatis-3-mapper.dtd"><mappernamespace="mapper.UserMapper"><insertid="addUser"parameterType="pojo.User">        insert into user(id, name, password)
        values (#{id}, #{name}, #{password})
</insert><deleteid="deleteUserById"parameterType="int">        delete
        from user
        where id = #{id}
</delete><updateid="updateUserById"parameterType="pojo.User">        update user
        set name=#{name},
            password = #{password}
        where id = #{id}
</update><selectid="selectUser"resultType="pojo.User">        select *
        from user
</select><selectid="selectUserByNameAndPassword"parameterType="map"resultType="pojo.User">        select *
        from user
        where name = #{name}
          and password = #{password}
</select></mapper>
  1. 编写 jdbc.properties 文件
url=jdbc:mysql://localhost:3306/mybatis?useUnicode=true&characterEncoding=utf8
driver=com.mysql.jdbc.Driver
username=root
password=123456
  1. 编写MyBatis核心配置文件
<?xmlversion="1.0" encoding="UTF-8" ?><!DOCTYPE configurationPUBLIC "-//mybatis.org//DTD Config 3.0//EN""http://mybatis.org/dtd/mybatis-3-config.dtd"><configuration><propertiesresource="jdbc.properties"/><!--环境配置--><environmentsdefault="development"><environmentid="development"><transactionManagertype="JDBC"/><dataSourcetype="POOLED"><propertyname="driver"value="${driver}"/><propertyname="url"value="${url}"/><propertyname="username"value="${username}"/><propertyname="password"value="${password}"/></dataSource></environment></environments><!--注册 Mapper--><mappers><mapperclass="mapper.UserMapper"/></mappers></configuration>
  1. 编写 MybatisUtils 工具类
importorg.apache.ibatis.io.Resources;
importorg.apache.ibatis.session.SqlSession;
importorg.apache.ibatis.session.SqlSessionFactory;
importorg.apache.ibatis.session.SqlSessionFactoryBuilder;
importjava.io.IOException;
importjava.io.InputStream;
publicclassMybatisUtils {
/*** 通过输入流从 XML 文件中构建 SqlSessionFactory 的实例*/privatestaticSqlSessionFactorysqlSessionFactory;
static {
try {
InputStreaminputStream=Resources.getResourceAsStream("mybatis-config.xml");
sqlSessionFactory=newSqlSessionFactoryBuilder().build(inputStream);
        } catch (IOExceptione) {
e.printStackTrace();
        }
    }
/*** 获取 SqlSession 连接,并设置自动提交事务** @return SqlSession连接*/publicstaticSqlSessiongetSession() {
returnsqlSessionFactory.openSession(true);
    }
  1. 编写测试类
importlombok.Cleanup;
importorg.apache.ibatis.session.SqlSession;
importorg.junit.Test;
importjava.util.HashMap;
importjava.util.List;
importjava.util.Map;
publicclassUserMapperTest {
@TestpublicvoidaddUser() {
@CleanupSqlSessionsqlSession=MybatisUtils.getSession();
UserMapperuserMapper=sqlSession.getMapper(UserMapper.class);
intnum=userMapper.addUser(newUser(3, "tes", "123123"));
System.out.println(num);
    }
@TestpublicvoiddeleteUserById() {
@CleanupSqlSessionsqlSession=MybatisUtils.getSession();
UserMapperuserMapper=sqlSession.getMapper(UserMapper.class);
intnum=userMapper.deleteUserById(3);
System.out.println(num);
    }
@TestpublicvoidupdateUserById() {
@CleanupSqlSessionsqlSession=MybatisUtils.getSession();
UserMapperuserMapper=sqlSession.getMapper(UserMapper.class);
intnum=userMapper.updateUserById(newUser(1, "admin", "123465"));
System.out.println(num);
    }
@TestpublicvoidselectUser() {
@CleanupSqlSessionsqlSession=MybatisUtils.getSession();
UserMapperuserMapper=sqlSession.getMapper(UserMapper.class);
List<User>userList=userMapper.selectUser();
for (Useruser : userList) {
System.out.println(user);
        }
    }
@TestpublicvoidselectUserByNameAndPassword() {
@CleanupSqlSessionsqlSession=MybatisUtils.getSession();
UserMapperuserMapper=sqlSession.getMapper(UserMapper.class);
Map<String, Object>map=newHashMap<>();
map.put("name", "test");
map.put("password", "test");
Useruser=userMapper.selectUserByNameAndPassword(map);
System.out.println(user);
    }
}
  1. 运行测试,成功的查询出来的我们的数据,ok!

XML 配置

<!--配置--><configuration><!--属性--><properties><!--设置--><settings><!--类型别名--><typeAliases><!--类型处理器--><typeHandlers><!--对象工厂--><objectFactory><!--插件--><plugins><!--环境配置--><environments><!--数据库厂商标识--><databaseIdProvider><!--映射器--><mappers></configuration><!-- 注意元素节点的顺序!顺序不对会报错 -->

Properties

属性可以在外部文件进行配置,通过 <properties resource="jdbc.properties"/>引入外部文件

url=jdbc:mysql://localhost:3306/mybatis?useUnicode=true&characterEncoding=utf8
driver=com.mysql.jdbc.Driver
username=root
password=123456
<propertiesresource="jdbc.properties"/><!--环境配置--><environmentsdefault="development"><environmentid="development"><transactionManagertype="JDBC"/><dataSourcetype="POOLED"><propertyname="driver"value="${driver}"/><propertyname="url"value="${url}"/><propertyname="username"value="${username}"/><propertyname="password"value="${password}"/></dataSource></environment></environments

也可以在 properties 元素的子元素中设置,并可以进行动态替换

<!--环境配置--><environmentsdefault="development"><environmentid="development"><transactionManagertype="JDBC"/><dataSourcetype="POOLED"><propertyname="driver"value="com.mysql.jdbc.Driver"/><propertyname="url"value="jdbc:mysql://localhost:3306/mybatis?useUnicode=true&amp;characterEncoding=utf8"/><propertyname="username"value="root"/><propertyname="password"value="123456"/></dataSource></environment></environments>

Settings

常用的设置

设置名 描述 有效值 默认值
cacheEnabled 全局性地开启或关闭所有映射器配置文件中已配置的任何缓存。 true | false true
lazyLoadingEnabled 延迟加载的全局开关。当开启时,所有关联对象都会延迟加载。 true | false false
logImpl 指定 MyBatis 所用日志的具体实现,未指定时将自动查找。 SLF4J | LOG4J | LOG4J2 | JDK_LOGGING | COMMONS_LOGGING | STDOUT_LOGGING | NO_LOGGING 未设置

一个配置完整的 settings 元素的示例如下:

<settings><settingname="cacheEnabled"value="true"/><settingname="lazyLoadingEnabled"value="true"/><settingname="multipleResultSetsEnabled"value="true"/><settingname="useColumnLabel"value="true"/><settingname="useGeneratedKeys"value="false"/><settingname="autoMappingBehavior"value="PARTIAL"/><settingname="autoMappingUnknownColumnBehavior"value="WARNING"/><settingname="defaultExecutorType"value="SIMPLE"/><settingname="defaultStatementTimeout"value="25"/><settingname="defaultFetchSize"value="100"/><settingname="safeRowBoundsEnabled"value="false"/><settingname="mapUnderscoreToCamelCase"value="false"/><settingname="localCacheScope"value="SESSION"/><settingname="jdbcTypeForNull"value="OTHER"/><settingname="lazyLoadTriggerMethods"value="equals,clone,hashCode,toString"/></settings>

TypeAliases

类型别名是为 Java 类型设置一个短的名字。它只和 XML 配置有关,存在的意义仅在于用来减少类完全限定名的冗余。

  1. 扫描类使用别名
<typeAliases><typeAliastype="pojo.User"alias="User"/></typeAliases>
  1. 扫描包
<typeAliases><packagename="pojo"/></typeAliases>
  1. 每一个在包 pojo 中的 JavaBean,在没有注解的情况下,会使用 Bean 的首字母小写的非限定类名来作为它的别名。若有注解,则别名为其注解值。
  1. 在类上使用注解
importorg.apache.ibatis.type.Alias;
@Alias("user")
publicclassUser {
privateintid;
privateStringname;
privateStringpassword;
}

下面是一些为常见的 Java 类型内建的类型别名。它们都是不区分大小写的,注意,为了应对原始类型的命名重复,采取了特殊的命名风格。

别名 映射的类型
_byte byte
_long long
_short short
_int int
_integer int
_double double
_float float
_boolean boolean
string String
byte Byte
long Long
short Short
int Integer
integer Integer
double Double
float Float
boolean Boolean
date Date
decimal BigDecimal
bigdecimal BigDecimal
object Object
map Map
hashmap HashMap
list List
arraylist ArrayList
collection Collection
iterator Iterator

Environments

配置 MyBatis 的多套运行环境,将 SQL 映射到多个不同的数据库上,必须指定其中一个为默认运行环境(通过 default 指定),通过 id 进行区别

<!--环境配置--><environmentsdefault="development"><!--development环境--><environmentid="development"><transactionManagertype="JDBC"/><dataSourcetype="POOLED"><propertyname="driver"value="${driver}"/><propertyname="url"value="${url}"/><propertyname="username"value="${username}"/><propertyname="password"value="${password}"/></dataSource></environment><!--test--><environmentid="test"><transactionManagertype="JDBC"/><dataSourcetype="POOLED"><propertyname="driver"value="${driver}"/><propertyname="url"value="${url}"/><propertyname="username"value="${username}"/><propertyname="password"value="${password}"/></dataSource></environment></environments>

子元素节点:environment

  1. transactionManager 事务管理器
<!--语法--><transactionManagertype="[ JDBC | MANAGED ]"/>
  1. dataSource 数据源
  1. 使用标准的 JDBC 数据源接口来配置 JDBC 连接对象的资源。数据源是必须配置的。有三种内建的数据源类型:
  • unpooled:这个数据源的实现只是每次被请求时打开和关闭连接。
  • pooled:这种数据源的实现利用“池”的概念将 JDBC 连接对象组织起来 , 这是一种使得并发 Web 应用快速响应请求的流行处理方式。
  • jndi:这个数据源的实现是为了能在如 Spring 或应用服务器这类容器中使用,容器可以集中或在外部配置数据源,然后放置一个 JNDI 上下文的引用。



Mapper

Mapper 映射器告诉 MyBatis 到哪里去找映射文件

  1. 使用相对于类路径的资源引用
<mappers><mapperresource="mapper/UserMapper.xml"/></mappers>
  1. 类名
<!--需要配置文件名称和接口名称一致,并且位于同一目录下--><mappers><mapperclass="mapper.UserMapper"/></mappers>
  1. 包名
<!--需要配置文件名称和接口名称一致,并且位于同一目录下--><mappers><packagename="mapper"/></mappers>

Mapper 文件

<?xmlversion="1.0"encoding="UTF-8"?><!DOCTYPEmapperPUBLIC"-//mybatis.org//DTD Mapper 3.0//EN""http://mybatis.org/dtd/mybatis-3-mapper.dtd"><mappernamespace="mapper.UserMapper"><insertid="addUser"parameterType="pojo.User">insertintouser(id, name, password)
values (#{id}, #{name}, #{password})
</insert></mapper>
  • namespace 的命名必须跟某个接口同名
  • 接口中的方法与映射文件中sql语句id应该一一对应
  • namespace命名规则 : 包名+类名

XML 映射

ParameterType

  1. 一个基本类型的参数,可以通过 #{参数名} 获取
<?xmlversion="1.0" encoding="UTF-8" ?><!DOCTYPE mapperPUBLIC "-//mybatis.org//DTD Mapper 3.0//EN""http://mybatis.org/dtd/mybatis-3-mapper.dtd"><mappernamespace="mapper.UserMapper"><deleteid="deleteUserById"parameterType="int">        delete
        from user
        where id = #{id}
</delete></mapper>
  1. 多个参数时采用Map,通过 #{Key} 获取
Map<String,Object> map = new HashMap<>();
map.put("name", "test");
map.put("password", "test");
User user = userMapper.selectUserByNameAndPassword(map);
<?xmlversion="1.0" encoding="UTF-8" ?><!DOCTYPE mapperPUBLIC "-//mybatis.org//DTD Mapper 3.0//EN""http://mybatis.org/dtd/mybatis-3-mapper.dtd"><mappernamespace="mapper.UserMapper"><selectid="selectUserByNameAndPassword"parameterType="map"resultType="pojo.User">        select *
        from user
        where name = #{name}
          and password = #{password}
</select></mapper>
  1. 对象传递参数,通过 #{属性名} 获取
<?xmlversion="1.0" encoding="UTF-8" ?><!DOCTYPE mapperPUBLIC "-//mybatis.org//DTD Mapper 3.0//EN""http://mybatis.org/dtd/mybatis-3-mapper.dtd"><mappernamespace="mapper.UserMapper"><insertid="addUser"parameterType="pojo.User">        insert into user(id, name, password)
        values (#{id}, #{name}, #{password})
</insert></mapper>
  1. 使用注解,在接口方法的参数前加 @Param() 属性,直接取 @Param() 中设置的值即可,不需要单独设置参数类型
import org.apache.ibatis.annotations.Param;
public interface UserMapper {
    int deleteUserById(@Param("id") int id);
}
<?xmlversion="1.0" encoding="UTF-8" ?><!DOCTYPE mapperPUBLIC "-//mybatis.org//DTD Mapper 3.0//EN""http://mybatis.org/dtd/mybatis-3-mapper.dtd"><mappernamespace="mapper.UserMapper"><deleteid="deleteUserById">        delete
        from user
        where id = #{id}
</delete></mapper>

关于@Param

使用 @Param 注解用于给方法参数起一个名字。使用原则:

  • 在方法只接受一个参数的情况下,可以不使用 @Param。
  • 在方法接受多个参数的情况下,建议一定要使用 @Param 注解给参数命名。
  • 如果参数是 JavaBean , 则不能使用@Param。
  • 不使用 @Param 注解时,参数只能有一个,并且是Javabean。

#与$的区别

  • #{} 的作用主要是替换预编译语句(PrepareStatement)中的占位符? 【推荐使用】
INSERT INTO user (name) VALUES (#{name});
INSERT INTO user (name) VALUES (?);
  • ${} 的作用是直接进行字符串替换
INSERT INTO user (name) VALUES ('${name}');
INSERT INTO user (name) VALUES ('zhangsan');

ResultType

自动映射

  • resultMap 元素是 MyBatis 中最重要最强大的元素。它可以让你从 90% 的 JDBC ResultSets 数据提取代码中解放出来。
  • 实际上,在为一些比如连接的复杂语句编写映射代码的时候,一份 resultMap 能够代替实现同等功能的长达数千行的代码。
  • ResultMap 的设计思想是,对于简单的语句根本不需要配置显式的结果映射,而对于复杂一点的语句只需要描述它们的关系就行了

简单映射语句示例,不需要显指定 resultMap。比如:

<?xmlversion="1.0" encoding="UTF-8" ?><!DOCTYPE mapperPUBLIC "-//mybatis.org//DTD Mapper 3.0//EN""http://mybatis.org/dtd/mybatis-3-mapper.dtd"><mappernamespace="mapper.UserMapper"><selectid="selectUserById"resultType="map">        select id , name , pwd
        from user
        where id = #{id}
</select></mapper>

上述语句只是简单地将所有的列映射到 HashMap 的键上,这由 resultType 属性指定。虽然在大部分情况下都够用,但是 HashMap 不是一个很好的模型。你的程序更可能会使用 JavaBean 或 POJO(Plain Old Java Objects,普通老式 Java 对象)作为模型。


手动映射

返回值类型为 resultMap,编写 resultMap,实现手动映射!

<?xmlversion="1.0" encoding="UTF-8" ?><!DOCTYPE mapperPUBLIC "-//mybatis.org//DTD Mapper 3.0//EN""http://mybatis.org/dtd/mybatis-3-mapper.dtd"><mappernamespace="mapper.UserMapper"><!-- 编写 resultMap --><resultMapid="UserMaps"type="pojo.User"><!-- id为主键 --><idcolumn="id"property="id"/><!-- column是数据库表的列名 , property是对应实体类的属性名 --><resultcolumn="name"property="name"/><resultcolumn="password"property="password"/></resultMap><!-- 返回值类型为 resultMap ,值为编写的 resultMap 的 id --><selectid="selectUser"resultMap="UserMaps">        select *
        from user
</select></mapper>

注解开发

适用于简单的 SQL 语句,利用注解开发就不需要 mapper.xml 映射文件

  1. 在接口中添加注解
packagemapper;
importorg.apache.ibatis.annotations.*;
importpojo.User;
importjava.util.List;
publicinterfaceUserMapper {
@Insert("insert into user(id, name, password) values (#{id}, #{name}, #{password})")
intaddUser(Useruser);
@Delete("delete from user where id=#{id}")
intdeleteUserById(@Param("id") intid);
@Update("update user set name=#{name},password=#{password} where id = #{id}")
intupdateUserById(Useruser);
@Select("select id,name,password from user")
List<User>selectUser();
}
  1. 在mybatis的核心配置文件中注入
<!--注册Mapper--><mappers><mapperclass="mapper.UserMapper"/></mappers>
  1. 测试 OK 即可

一对多

多个学生对应一个老师

  1. 创建学生实体类
@DatapublicclassStudent {
privateintid;
privateStringname;
//多个学生可以是同一个老师,即多对一privateTeacherteacher;
}
  1. 编写 StudentMapper 接口
publicinterfaceStudentMapper {
//获取所有学生及对应老师的信息publicList<Student>getStudents();
}
  1. 编写 Mapper.xml 文件
<?xmlversion="1.0" encoding="UTF-8" ?><!DOCTYPE mapperPUBLIC "-//mybatis.org//DTD Mapper 3.0//EN""http://mybatis.org/dtd/mybatis-3-mapper.dtd"><mappernamespace="mapper.StudentMapper"><!--需求:获取所有学生及对应老师的信息--><!--思路1:嵌套查询association → 一个复杂类型的关联;使用它来处理关联查询--><selectid="getStudents"resultMap="StudentTeacher">        select * from student
</select><resultMapid="StudentTeacher"type="Student"><!--association关联属性 property→属性名 javaType→属性类型 column→关联列名 select→子查询--><associationproperty="teacher"column="tid"javaType="Teacher"select="getTeacher"/></resultMap><!-- 传递一个参数的时候,下面可以写任何值 --><selectid="getTeacher"resultType="teacher">        select * from teacher where id = #{id}
</select><!----------------------------------------------------------------------------------------------------><!--思路2:结果集的映射--><selectid="getStudents2"resultMap="StudentTeacher2">        select s.id sid, s.name sname , t.name tname
        from student s,teacher t
        where s.tid = t.id
</select><resultMapid="StudentTeacher2"type="Student"><idproperty="id"column="sid"/><resultproperty="name"column="sname"/><!--association关联属性 property→属性名 javaType→属性类型--><associationproperty="teacher"javaType="Teacher"><!-- column是数据库表的列名 , property是对应实体类的属性名 --><resultproperty="name"column="tname"/></association></resultMap></mapper>

注:当子查询需要传多个参数时

<?xmlversion="1.0" encoding="UTF-8" ?><!DOCTYPE mapperPUBLIC "-//mybatis.org//DTD Mapper 3.0//EN""http://mybatis.org/dtd/mybatis-3-mapper.dtd"><mappernamespace="mapper.StudentMapper"><selectid="getStudents"resultMap="StudentTeacher">        select * from student
</select><!-- 传递多个参数时association 中 column 多参数配置:column="{key=value,key=value}"其实就是键值对的形式,key 是传给下个 sql 的取值名称,value 是片段一中 sql 查询的字段名--><resultMapid="StudentTeacher"type="Student"><!--association关联属性 property→属性名 javaType→属性类型 column→关联列名 select→子查询--><associationproperty="teacher"column="{id=tid,name=tname}"javaType="Teacher"select="getTeacher"/></resultMap><selectid="getTeacher"resultType="teacher">        select * from teacher where id = #{id} and name = #{name}
</select></mapper>

多对一

一个老师对应多个学生

  1. 创建教师实体类
@DatapublicclassTeacher {
privateintid;
privateStringname;
//一个老师多个学生privateList<Student>students;
}
  1. 编写 TeacherMapper 接口
publicinterfaceTeacherMapper {
//获取指定老师,及老师下的所有学生publicTeachergetTeacher(intid);
}
  1. 编写 Mapper.xml 文件
<?xmlversion="1.0" encoding="UTF-8" ?><!DOCTYPE mapperPUBLIC "-//mybatis.org//DTD Mapper 3.0//EN""http://mybatis.org/dtd/mybatis-3-mapper.dtd"><mappernamespace="mapper.TeacherMapper"><!--需求:获取指定老师,及老师下的所有学生--><!--思路1:嵌套查询--><selectid="getTeacher"resultMap="TeacherStudent2">        select * from teacher where id = #{id}
</select><resultMapid="TeacherStudent2"type="Teacher"><!--column是 student 表外键的列名--><collectionproperty="students"javaType="ArrayList"ofType="Student"column="id"select="getStudentByTeacherId"/></resultMap><selectid="getStudentByTeacherId"resultType="Student">        select * from student where tid = #{id}
</select><!----------------------------------------------------------------------------------------------------><!--思路2:结果集映射集合映射使用 collection!JavaType 和 ofType都是用来指定对象类型的JavaType是用来指定 pojo 中属性的类型ofType 指定的是映射到 list集合属性中 pojo的类型。--><selectid="getTeacher"resultMap="TeacherStudent">        select s.id sid, s.name sname , t.name tname, t.id tid
        from student s,teacher t
        where s.tid = t.id and t.id=#{id}
</select><resultMapid="TeacherStudent"type="Teacher"><resultproperty="name"column="tname"/><collectionproperty="students"ofType="Student"><resultproperty="id"column="sid"/><resultproperty="name"column="sname"/><resultproperty="tid"column="tid"/></collection></resultMap></mapper>

动态SQL

MyBatis 的强大特性之一便是它的动态 SQL。如果你有使用 JDBC 或其它类似框架的经验,你就能体会到根据不同条件拼接 SQL 语句的痛苦。例如拼接时要确保不能忘记添加必要的空格,还要注意去掉列表最后一个列名的逗号。利用动态 SQL 这一特性可以彻底摆脱这种痛苦。

虽然在以前使用动态 SQL 并非一件易事,但正是 MyBatis 提供了可以被用在任意 SQL 映射语句中的强大的动态 SQL 语言得以改进这种情形。

动态 SQL 元素和 JSTL 或基于类似 XML 的文本处理器相似。在 MyBatis 之前的版本中,有很多元素需要花时间了解。MyBatis 3 大大精简了元素种类,现在只需学习原来一半的元素便可。MyBatis 采用功能强大的基于 OGNL 的表达式来淘汰其它大部分元素。

IF 语句

<!--如果名字为空,那么只根据密码查询,反之,则根据名字来查询select * from blog where name = #{name} and password = #{password}--><selectid="selectUserByNameAndPassword"parameterType="map"resultType="pojo.User">    select *
    from user
    where
<iftest="name!=null">        name = #{name}
</if><iftest="password!=null">        and password = #{password}
</if></select>

上述语句中,如果 title 为空那么查询语句为 select * from user where and password = #{password} ,这是错误的 SQL 语句

Where 语句

<!--如果名字为空,那么只根据密码查询,反之,则根据名字来查询select * from blog where name = #{name} and password = #{password}--><selectid="selectUserByNameAndPassword"parameterType="map"resultType="pojo.User">    select *
    from user
<where><iftest="name != null">            name = #{name}
</if><iftest="password != null">            and password = #{password}
</if></where></select>

“where”标签:如果它包含的标签中有返回值的话,它就会插入一个‘where’。此外,如果标签返回的内容是以 AND 或 OR 开头的,则它会将其剔除掉。

Set 语句

<!--update user set name=#{name},password=#{password} where id=#{id}--><updateid="updateUserById"parameterType="pojo.User">    update user
<set><iftest="name!=null">            name=#{name},
</if><iftest="password!=null">            password = #{password}
</if></set>    where id = #{id}
</update>

Choose语句

类似于 Java 的 switch 语句,查询条件有一个满足即可。

<selectid="selectUserByNameAndPassword"parameterType="map"resultType="pojo.User">    select *
    from user
<where><choose><whentest="name != null">                name = #{name}
</when><whentest="password != null">                and password = #{password}
</when><otherwise>                and id=#{id}
</otherwise></choose></where></select>

foreach

foreach 元素的功能是非常强大的,它允许你指定一个集合,声明可以用在元素体内的集合项和索引变量。它也允许你指定开闭匹配的字符串以及在迭代中间放置分隔符。这个元素是很智能的,因此它不会偶然地附加多余的分隔符。

动态 SQL 的另外一个常用的必要操作是需要对一个集合进行遍历,通常是在构建 IN 条件语句的时候。比如:

<!--select * from user where id in (1,2,3)--><selectid="selectUser"resultType="pojo.User">    select *
    from user
    where id in
<!--List 实例将会以 "list" 作为键数组实例的键是 "array"--><foreachcollection="array"item="id"open="("separator=","close=")">        #{id}
</foreach></select>
  • collection:指定输入对象中的集合属性
  • item:每次遍历生成的对象
  • open:开始遍历时的拼接字符串
  • close:结束时拼接的字符串
  • separator:遍历对象之间需要拼接的字符串

注意:可以将一个 List 实例或者数组作为参数对象传给 MyBatis,当你这么做的时候,MyBatis 会自动将它包装在一个 Map 中并以名称为键。List 实例将会以 “list” 作为键,而数组实例的键将是 “array”。


SQL片段

有时候可能某个 sql 语句我们用的特别多,为了增加代码的重用性,简化代码,我们需要将这些代码抽取出来,然后使用时直接调用。

  • 提取重复 SQL 片段:
<sqlid="if-name-password"><iftest="name != null">        name = #{name}
</if><iftest="password != null">        and password = #{password}
</if></sql>
  • 引用SQL片段:
<selectid="selectUserByNameAndPassword"parameterType="map"resultType="pojo.User">  select * from user
<where><!-- 引用 sql 片段,如果 refid 指定的不在本文件中,那么需要在前面加上 namespace --><includerefid="if-name-password"></include><!-- 在这里还可以引用其他的 sql 片段 --></where></select>


注意:

  1. 最好基于单表来定义 sql 片段,提高片段的可重用性
  2. 在 sql 片段中不要包括 where

日志

内置日志

在 Mybatis 核心配置文件中设置即可

<settings><settingname="logImpl"value="STDOUT_LOGGING"/></settings>

Log4j

Log4j 是 Apache 的一个开源项目,通过使用 Log4j,可以控制日志信息输送的目的地是控制台、文件、GUI组件,我们也可以控制每一条日志的输出格式;通过定义每一条日志信息的级别,我们能够更加细致地控制日志的生成过程。

  1. 导入 Log4j 依赖
<dependency><groupId>log4j</groupId><artifactId>log4j</artifactId><version>1.2.17</version></dependency>
  1. 为了实现只保留7天日志文件,扩展 DailyRollingFileAppender
importjava.io.File;
importjava.io.FileFilter;
importjava.io.IOException;
importjava.io.InterruptedIOException;
importjava.text.ParseException;
importjava.text.SimpleDateFormat;
importjava.util.Arrays;
importjava.util.Calendar;
importjava.util.Date;
importjava.util.GregorianCalendar;
importjava.util.Locale;
importjava.util.TimeZone;
importorg.apache.log4j.FileAppender;
importorg.apache.log4j.Layout;
importorg.apache.log4j.helpers.LogLog;
importorg.apache.log4j.spi.LoggingEvent;
/*** Log4j的扩展-保留最近指定个数的日志文件**/publicclassMyDailyRollingFileAppenderextendsFileAppender{
staticfinalintTOP_OF_TROUBLE=-1;
staticfinalintTOP_OF_MINUTE=0;
staticfinalintTOP_OF_HOUR=1;
staticfinalintHALF_DAY=2;
staticfinalintTOP_OF_DAY=3;
staticfinalintTOP_OF_WEEK=4;
staticfinalintTOP_OF_MONTH=5;
/*** 默认设置:"'.'yyyy-MM-dd"=* 设置说明:按天循环打印日志*/privateStringdatePattern="'.'yyyy-MM-dd";
privateintmaxBackupIndex=1;
privateStringscheduledFilename;
/**下一次Rolling日志文件的时间*/privatelongnextCheck=System.currentTimeMillis () -1;
Datenow=newDate();
SimpleDateFormatsdf;
RollingCalendarrc=newRollingCalendar();
intcheckPeriod=TOP_OF_TROUBLE;
/*** 获取当前环境所处的时区* 仅供computeCheckPeriod方法使用*/staticfinalTimeZonegmtTimeZone=TimeZone.getTimeZone("GMT");
publicMyDailyRollingFileAppender() {}
publicMyDailyRollingFileAppender (Layoutlayout, Stringfilename, StringdatePattern) throwsIOException {
super(layout, filename, true);
this.datePattern=datePattern;
activateOptions();
    }
publicStringgetDatePattern() {
returndatePattern;
    }
publicvoidsetDatePattern(Stringpattern) {
this.datePattern=pattern;
    }
publicintgetMaxBackupIndex() {
returnmaxBackupIndex;
    }
publicvoidsetMaxBackupIndex(intmaxBackupIndex) {
this.maxBackupIndex=maxBackupIndex;
    }
/*** activateOptions译名为激活操作* 意思是按照配置的参数进行初始化* scheduledFilename为log的最后一次修改时间*/@OverridepublicvoidactivateOptions() {
super.activateOptions();
if(datePattern!=null&&fileName!=null) {
now.setTime(System.currentTimeMillis());
sdf=newSimpleDateFormat(datePattern);
inttype=computeCheckPeriod();
printPeriodicity(type);
rc.setType(type);
Filefile=newFile(fileName);
scheduledFilename=fileName+sdf.format(newDate(file.lastModified()));
        } else {
LogLog.error("Either File or DatePattern options are not set for appender ["+name+"].");
        }
    }
intcomputeCheckPeriod() {
RollingCalendarrollingCalendar=newRollingCalendar(gmtTimeZone, Locale.getDefault());
//设置初始时间为格林尼治时间:1970-01-01 00:00:00 GMTDateepoch=newDate(0);
if(datePattern!=null) {
for(inti=TOP_OF_MINUTE; i<=TOP_OF_MONTH; i++) {
//将所示的时间格式化为当前时区SimpleDateFormatsimpleDateFormat=newSimpleDateFormat(datePattern);
simpleDateFormat.setTimeZone(gmtTimeZone);
Stringr0=simpleDateFormat.format(epoch);
rollingCalendar.setType(i);
Datenext=newDate(rollingCalendar.getNextCheckMillis(epoch));
Stringr1=simpleDateFormat.format(next);
//System.out.println("Type = "+i+", r0 = "+r0+", r1 = "+r1);if(r0!=null&&r1!=null&&!r0.equals(r1)) {
returni;
                }
            }
        }
returnTOP_OF_TROUBLE; // Deliberately head for trouble...    }
/*** 按照周期将当前日志文件转存为日期文件** @throws IOException*/voidrollOver() throwsIOException {
if (datePattern==null) {
errorHandler.error("Missing DatePattern option in rollOver().");
return;
        }
StringdatedFilename=fileName+sdf.format(now);
//如果最后一次的修改时间为当前时间 ,则不做任何任何操作if (scheduledFilename.equals(datedFilename)) {
return;
        }
// 关闭当前文件,重命名为日期文件this.closeFile();
Filetarget=newFile(scheduledFilename);
if (target.exists()) {
target.delete();
        }
Filefile=newFile(fileName);
booleanresult=file.renameTo(target);
if(result) {
LogLog.debug(fileName+" -> "+scheduledFilename);
        } else {
LogLog.error("Failed to rename ["+fileName+"] to ["+scheduledFilename+"].");
        }
//获取日志文件列表,控制数量,实现清理策略if (file.getParentFile().exists()){
File[] files=file.getParentFile().listFiles(newLogFileFilter(file.getName()));
Long[] dateArray=newLong[files.length];
for (inti=0; i<files.length; i++) {
FilefileItem=files[i];
StringfileDateStr=fileItem.getName().replace(file.getName(), "");
Datefiledate=null;
try {
filedate=sdf.parse(fileDateStr);
longfileDateLong=filedate.getTime();
dateArray[i] =fileDateLong;
                } catch (ParseExceptione) {
LogLog.error("Parse File Date Throw Exception : "+e.getMessage());
                }
            }
Arrays.sort(dateArray);
if (dateArray.length>maxBackupIndex) {//需要清理多余日志文件了for (inti=0; i<dateArray.length-maxBackupIndex; i++) {
StringdateFileName=file.getPath() +sdf.format(dateArray[i]);
FiledateFile=newFile(dateFileName);
if (dateFile.exists()) {
dateFile.delete();
                    }
                }
            }
        }
try {
this.setFile(fileName, true, this.bufferedIO, this.bufferSize);
        }
catch(IOExceptione) {
errorHandler.error("setFile("+fileName+", true) call failed.");
        }
scheduledFilename=datedFilename;
    }
/*** 写入日志之前判断是否需要新起一个日志来记录*/@OverrideprotectedvoidsubAppend(LoggingEventevent) {
longn=System.currentTimeMillis();
if (n>=nextCheck) {
now.setTime(n);
nextCheck=rc.getNextCheckMillis(now);
try {
rollOver();
            } catch(IOExceptionioe) {
if (ioeinstanceofInterruptedIOException) {
Thread.currentThread().interrupt();
                }
LogLog.error("rollOver() failed.", ioe);
            }
        }
super.subAppend(event);
    }
/*** 文件过滤器*/classLogFileFilterimplementsFileFilter {
privateStringlogName;
publicLogFileFilter(StringlogName) {
this.logName=logName;
        }
@Overridepublicbooleanaccept(Filefile) {
if (logName==null||file.isDirectory()) {
returnfalse;
            } else {
LogLog.debug(file.getName());
returnfile.getName().startsWith(logName);
            }
        }
    }
/*** 根据type打印做出日志打印* @param type*/voidprintPeriodicity(inttype) {
switch(type) {
caseTOP_OF_MINUTE:
LogLog.debug("Appender ["+name+"] to be rolled every minute.");
break;
caseTOP_OF_HOUR:
LogLog.debug("Appender ["+name+"] to be rolled on top of every hour.");
break;
caseHALF_DAY:
LogLog.debug("Appender ["+name+"] to be rolled at midday and midnight.");
break;
caseTOP_OF_DAY:
LogLog.debug("Appender ["+name+"] to be rolled at midnight.");
break;
caseTOP_OF_WEEK:
LogLog.debug("Appender ["+name+"] to be rolled at start of week.");
break;
caseTOP_OF_MONTH:
LogLog.debug("Appender ["+name+"] to be rolled at start of every month.");
break;
default:
LogLog.warn("Unknown periodicity for appender ["+name+"].");
        }
    }
/*** 内部类* @author 日志Rolling日历,获取下一次日志Rolling时间**/classRollingCalendarextendsGregorianCalendar {
privatestaticfinallongserialVersionUID=-3560331770601814177L;
inttype=MyDailyRollingFileAppender.TOP_OF_TROUBLE;
/*** RollingCalendar默认构造器*/RollingCalendar() {
super();
        }
/*** RollingCalendar构造器* 根据地点时区 ,获取对应的日历Calendar* @param tz* @param locale*/RollingCalendar(TimeZonetz, Localelocale) {
super(tz, locale);
        }
voidsetType(inttype) {
this.type=type;
        }
publiclonggetNextCheckMillis(Datenow) {
returngetNextCheckDate(now).getTime();
        }
/*** 根据所传入的时间以及时间类型获取下一个时间* @param now* @return*/publicDategetNextCheckDate(Datenow) {
this.setTime(now);
switch(type) {
caseMyDailyRollingFileAppender.TOP_OF_MINUTE:
this.set(Calendar.SECOND, 0);
this.set(Calendar.MILLISECOND, 0);
this.add(Calendar.MINUTE, 1);
break;
caseMyDailyRollingFileAppender.TOP_OF_HOUR:
this.set(Calendar.MINUTE, 0);
this.set(Calendar.SECOND, 0);
this.set(Calendar.MILLISECOND, 0);
this.add(Calendar.HOUR_OF_DAY, 1);
break;
caseMyDailyRollingFileAppender.HALF_DAY:
this.set(Calendar.MINUTE, 0);
this.set(Calendar.SECOND, 0);
this.set(Calendar.MILLISECOND, 0);
inthour=get(Calendar.HOUR_OF_DAY);
if(hour<12) {
this.set(Calendar.HOUR_OF_DAY, 12);
                    } else {
this.set(Calendar.HOUR_OF_DAY, 0);
this.add(Calendar.DAY_OF_MONTH, 1);
                    }
break;
caseMyDailyRollingFileAppender.TOP_OF_DAY:
this.set(Calendar.HOUR_OF_DAY, 0);
this.set(Calendar.MINUTE, 0);
this.set(Calendar.SECOND, 0);
this.set(Calendar.MILLISECOND, 0);
this.add(Calendar.DATE, 1);
break;
caseMyDailyRollingFileAppender.TOP_OF_WEEK:
this.set(Calendar.DAY_OF_WEEK, getFirstDayOfWeek());
this.set(Calendar.HOUR_OF_DAY, 0);
this.set(Calendar.MINUTE, 0);
this.set(Calendar.SECOND, 0);
this.set(Calendar.MILLISECOND, 0);
this.add(Calendar.WEEK_OF_YEAR, 1);
break;
caseMyDailyRollingFileAppender.TOP_OF_MONTH:
this.set(Calendar.DATE, 1);
this.set(Calendar.HOUR_OF_DAY, 0);
this.set(Calendar.MINUTE, 0);
this.set(Calendar.SECOND, 0);
this.set(Calendar.MILLISECOND, 0);
this.add(Calendar.MONTH, 1);
break;
default:
thrownewIllegalStateException("Unknown periodicity type.");
            }
returngetTime();
        }
    }
}
  1. 编写 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=utils.MyDailyRollingFileAppender
#日志文件路径
log4j.appender.file.File=./logs/log.log
log4j.appender.file.Encoding=utf-8
log4j.appender.file.Append=true
log4j.appender.file.Threshold=DEBUG
log4j.appender.file.layout=org.apache.log4j.PatternLayout
#设置日志文件名为日期并按天生成日志文件
log4j.appender.file.datePattern = '.'yyyy-MM-dd
#保留7天的日志文件
log4j.appender.file.maxBackupIndex=7
log4j.appender.file.layout.ConversionPattern=[%d{yyyy-MM-dd HH:mm:ss,SSS}][%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
  1. 运行测试,查看生成的日志文件

缓存

MyBatis包含一个非常强大的查询缓存特性,它可以非常方便地定制和配置缓存。缓存可以极大的提升查询效率。

MyBatis系统中默认定义了两级缓存:一级缓存二级缓存

  • 默认情况下,只有一级缓存开启。(SqlSession级别的缓存,也称为本地缓存)
  • 二级缓存需要手动开启和配置,他是基于namespace级别的缓存。
  • 为了提高扩展性,MyBatis定义了缓存接口Cache。可以通过实现Cache接口来自定义二级缓存

一级缓存

一级缓存也叫本地缓存:

  • 与数据库同一次会话期间查询到的数据会放在本地缓存中。
  • 以后如果需要获取相同的数据,直接从缓存中拿,没必须再去查询数据库;

二级缓存

二级缓存也叫全局缓存,一级缓存作用域太低了,所以诞生了二级缓存

  • 基于namespace级别的缓存,一个名称空间,对应一个二级缓存
  • 工作机制
  • 一个会话查询一条数据,这个数据就会被放在当前会话的一级缓存中;
  • 如果当前会话关闭了,这个会话对应的一级缓存就没了;但是我们想要的是,会话关闭了,一级缓存中的数据被保存到二级缓存中;
  • 新的会话查询信息,就可以从二级缓存中获取内容;
  • 不同的 mapper 查出的数据会放在自己对应的缓存(map)中;

使用步骤:

  1. 在 核心配置文件中开启全局缓存
<settingname="cacheEnabled"value="true"/>
  1. 每个 mapper.xml 中配置使用二级缓存
<cache/>

高级配置

<cacheeviction="FIFO"flushInterval="60000"size="512"readOnly="true"/>

这个更高级的配置创建了一个 FIFO 缓存,每隔 60 秒刷新,最多可以存储结果对象或列表的 512 个引用,而且返回的对象被认为是只读的,因此对它们进行修改可能会在不同线程中的调用者产生冲突。

可用的清除策略有:

  • LRU – 最近最少使用:移除最长时间不被使用的对象。
  • FIFO – 先进先出:按对象进入缓存的顺序来移除它们。
  • SOFT – 软引用:基于垃圾回收器状态和软引用规则移除对象。
  • WEAK – 弱引用:更积极地基于垃圾收集器状态和弱引用规则移除对象。

默认的清除策略是 LRU。

flushInterval(刷新间隔)属性可以被设置为任意的正整数,设置的值应该是一个以毫秒为单位的合理时间量。 默认情况是不设置,也就是没有刷新间隔,缓存仅仅会在调用语句时刷新。

size(引用数目)属性可以被设置为任意正整数,要注意欲缓存对象的大小和运行环境中可用的内存资源。默认值是 1024。

readOnly(只读)属性可以被设置为 true 或 false。只读的缓存会给所有调用者返回缓存对象的相同实例。 因此这些对象不能被修改。这就提供了可观的性能提升。而可读写的缓存会(通过序列化)返回缓存对象的拷贝。 速度上会慢一些,但是更安全,因此默认值是 false。

提示: 二级缓存是事务性的。这意味着,当 SqlSession 完成并提交时,或是完成并回滚,但没有执行 flushCache=true 的 insert/delete/update 语句时,缓存会获得更新。


问题

1、org.apache.ibatis.binding.BindingException: Type interface xxxxxxxxx is not known to the MapperRegistry.

  • 原因:Mapper.xml 未注册
  • 解决方法:在 Mybatis 核心配置文件中添加
<!--每一个 Mapper.xml 都需要再 Mybatis 核心配置文件中注册--><mappers><!--使用相对于类路径的资源引用--><mapperresource="mapper/UserMapper.xml"/><!--使用映射器接口实现类的完全限定类名--><mapperclass="mapper.UserMapper"/><!--将包内的映射器接口实现全部注册为映射器 --><packagename="mapper"/></mappers>

2、 Cause: org.apache.ibatis.builder.BuilderException: Error parsing SQL Mapper Configuration. Cause: java.io.IOException: Could not find resource xxxxxxxx

  • 原因:Maven 静态资源过滤问题
  • 解决方法:在 Maven pom.xml 配置文件中添加过滤
<build><resources><resource><directory>src/main/java</directory><includes><include>**/*.properties</include><include>**/*.xml</include></includes><filtering>true</filtering></resource><resource><directory>src/main/resources</directory><includes><include>**/*.properties</include><include>**/*.xml</include></includes><filtering>true</filtering></resource></resources></build>

3、org.apache.ibatis.cache.CacheException: Error serializing object. Cause: java.io.NotSerializableException: pojo.User


  • 原因:使用 开启二级缓存之后,readOnly 属性默认为 false,可读写的缓存会(通过序列化)返回缓存对象的拷贝,
  • 解决方法:实体类 实现 可序列化
publicclassUserimplementsSerializable {
privateintid;
privateStringname;
privateStringpassword;
}



相关实践学习
如何在云端创建MySQL数据库
开始实验后,系统会自动创建一台自建MySQL的 源数据库 ECS 实例和一台 目标数据库 RDS。
全面了解阿里云能为你做什么
阿里云在全球各地部署高效节能的绿色数据中心,利用清洁计算为万物互联的新世界提供源源不断的能源动力,目前开服的区域包括中国(华北、华东、华南、香港)、新加坡、美国(美东、美西)、欧洲、中东、澳大利亚、日本。目前阿里云的产品涵盖弹性计算、数据库、存储与CDN、分析与搜索、云通信、网络、管理与监控、应用服务、互联网中间件、移动服务、视频服务等。通过本课程,来了解阿里云能够为你的业务带来哪些帮助 &nbsp; &nbsp; 相关的阿里云产品:云服务器ECS 云服务器 ECS(Elastic Compute Service)是一种弹性可伸缩的计算服务,助您降低 IT 成本,提升运维效率,使您更专注于核心业务创新。产品详情: https://www.aliyun.com/product/ecs
目录
相关文章
|
6月前
|
Java 数据库连接 数据库
Mybatis逆向工程笔记小结
Mybatis逆向工程笔记小结
|
2月前
|
SQL Java 数据库连接
【Java笔记+踩坑】MyBatisPlus基础
MyBatisPlus简介、标准数据层开发CRUD、业务层继承IService、ServiceImpl、条件查询、LambdaQueryWrapper、id生成策略、逻辑删除、乐观锁@Version、代码生成器、ActiveRecord
【Java笔记+踩坑】MyBatisPlus基础
|
2月前
|
Java 数据库连接 数据格式
【Java笔记+踩坑】Spring基础2——IOC,DI注解开发、整合Mybatis,Junit
IOC/DI配置管理DruidDataSource和properties、核心容器的创建、获取bean的方式、spring注解开发、注解开发管理第三方bean、Spring整合Mybatis和Junit
【Java笔记+踩坑】Spring基础2——IOC,DI注解开发、整合Mybatis,Junit
|
6月前
|
SQL Java 关系型数据库
MyBatis-Plus全套笔记三
MyBatis-Plus全套笔记三
|
6月前
|
SQL Java 数据库连接
MyBatis-Plus全套笔记二
MyBatis-Plus全套笔记二
|
6月前
|
Java 关系型数据库 数据库连接
MyBatis-Plus全套笔记一
MyBatis-Plus全套笔记一
138 1
|
SQL Java 数据库连接
[推荐] MyBatis框架初学笔记-为之后拾遗
[推荐] MyBatis框架初学笔记-为之后拾遗
59 0
|
SQL 存储 缓存
MyBatis 学习心得笔记
MyBatis 学习心得笔记
96 0
下一篇
无影云桌面