MyBatis
Senior MyBatis
Published: 2021-04-13

对于MyBatis的学习总结

mybatis的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">
<configuration>
    <!--开启log4j的日志打印。默认就是开启的,可以不配置-->
    <settings>
        <!--当然,这个value除了是LOG4J还可以是SLF4J-->
    	<setting name="logImpl" value="LOG4J"/>
    </settings>
    <!--配置实体类的别名。工作中一般不用,因为一旦出问题就很难搞了-->
    <typeAliases>
    	<!--给某个实体类配置别名,在Mapper文件中使用别名即可表示对应的实体类,简化代码的编写-->
        <!-- <typeAlias type="com.bjsxt.pojo.Emp" alias="emp"></typeAlias> -->
        <!-- 但是上面这种写法还是很麻烦,设想如果有一百个实体类,那么我们还是要配置一百次设置别名,因此我们换用下面这种:-->
        <package name="com.bjsxt.pojo"/> <!-- 默认该包下的所有类的类名即为别名,不区分大小写。这意味着mapper配置文件中的类似com.bjsxt.pojo.Emp的写法都可以直接写成Emp或者emp -->
    </typeAliases>
	<!--配置数据库环境,default的值为某个environment的id的值,表示当前使用的数据库环境-->
    <environments default="mysql1">
        <!--表示一个具体的数据库环境,可以配置多个-->
    	<environment id="mysql1">
            <!--表明事务的管理仍然使用原生jdbc的方式-->
        	<transactionManager type="JDBC"></transactionManager>
            <!--配置数据库的连接参数,使用数据库连接池技术-->
            <!--有POOLED、UNPOOLED和JNDI,具体介绍请看本章节的dataSource介绍-->
            <dataSource type="POOLED">
            	<property name="driver" value="com.mysql.cj.jdbc.Driver"/>
            	<property name="url" value="jdbc:mysql://localhost:3306/db_name?serverTimezone=UTC"/>
            	<property name="username" value="root"/>
            	<property name="password" value="root"/>
            </dataSource>
        </environment>
    </environments>
    <!--配置Mapper文件的路径,用来告诉SqlSession mapper配置文件在哪,这样在最开始SqlSession就可以知道mapper配置文件有哪几个,就不需要在使用SqlSession做增删改查的时候再去找对应的mapper文件了-->
    <mappers>
    	<mapper resource="com/bjsxt/mapper/EmpMapper.xml"/>
    </mappers>
</configuration>

transactionManager

image-20210411185646240

在mybatis中有两种事务管理器类型(也就是type=”[JDBC|MANAGED]“);

JDBC - 这个配置直接简单实用了JDBC的提交和回滚设置。它依赖于从数据源得到的连接来管理事务范围

MANAGED - 这个配置几乎没做什么。它从来不提交或回滚一个连接。而它会让容器来管理事务的整个生命周期(比如Spring或JEE应用服务器的上下文)

dataSource

有三种内建的数据源类型(也就是 type=”???“):

  • UNPOOLED

    这个数据源的实现是每次被请求时简单打开和关闭连接。它有一点慢,这是对简单应用程序的一个很好的选择,因为它不需要及时的可用连接。UNPOOLED类型的数据源仅需配置以下5种属性:

    • driver

      这是JDBC驱动的Java类的完全限定名

    • url

      这是数据库的JDBC URL地址

    • usernama

      登录数据库的用户名

    • password

      登录数据库的密码

    • defaultTransactionIsolationLevel

      默认的连接事务隔离级别

  • POOLED

    这是JDBC连接对象的数据源连接池的实现,用来避免创建新的连接实例时必要的初始连接和认证时间。一种当前Web应用程序用来快速响应请求很流行的方法。除了上述提到UNPOOLED下的属性外,还有更多属性用来配置POOLED的数据源:

    • poolMaximumActiveConnections

      在任意时间可以存在的活动(也就是正在使用)连接数量,默认值:10

    • poolMaximumIdleConnections

      任意时间可能存在的空闲连接数

    • pool MaximumCheckoutTime

      在被强制返回之前,池中连接被检出(checked out)时间,默认值:20000毫秒(即20秒)

    • poolTimeToWait

      这是一个底层设置,如果获取连接花费了相当长的时间,连接池会打印状态日志并重新尝试获取一个连接(避免在误配置的情况下一直安静的失败),默认值:20000毫秒(即20秒)

    • poolMaximumLocalBadConnectionTolerance

      这是一个关于坏连接容忍度的底层设置,作用于每一个尝试从缓存池获取连接的线程。如果这个线程获取到的是一个坏的连接,那么这个数据源允许这个线程尝试重新获取一个新连接,但这个重新尝试次数不应超过poolMaximumIdleConnections与poolMaximumLocalBadConnectionTolerance之和。默认值:3(新增于3.4.5)

    • poolPingQuery

      发送到数据库的侦测查询,用来检验连接是否正常工作并准备接受请求。默认是”NO PING QUERY SET“,这会导致多数据库驱动失败时带有一个恰当的错误信息

    • poolPingEnabled

      是否启用侦测查询。若开启,需要设置poolPingQuery属性为一个可执行的SQL语句(最好是一个速度非常快的SQL语句),默认值:false。

    • poolPingConnectionsNotUsedFor

      配置poolPingQuery的频率。可以被设置为和数据库连接超时时间一样,来避免不必要的侦测,默认值:0(即所有连接每一时刻都被侦测,当然仅当poolPingEnabled为true时适用)

  • JNDI

    这个数据源实现是为了使用如Spring或应用服务器这类容器,容器可集中或在外部配置数据源,然后放置一个JNDI上下文的引用。这个数据源配置只需要两个属性。

    • initial_context

      这个属性用来在InitialContext种寻找上下文(即,initialContext.lookup(initial_context))。这是个可选属性,如果忽略,那么将会直接从InitialContext中寻找data_source属性。

    • data_source

      这是引用数据源实例位置的上下文的路径。提供了initial_context配置时会在其返回的上下文中进行查找,没有提供则直接在InitialContext中查找。

mappers

告诉Mybatis去哪里找映射文件

<!--使用相对于classpath的相对路径的资源,注意不是类,不使用. 而是使用/ -->
<mappers>
	<mapper resource="org/mybatis/builder/AuthorMapper.xml"/>
	<mapper resource="org/mybatis/builder/BlogMapper.xml"/>
	<mapper resource="org/mybatis/builder/PostMapper.xml"/>
</mappers>
<!--使用全限定路径,以file开始,指定本地的绝对路径-->
<mappers>
    <!--这里的配置文件可能不在项目内,甚至是在网上的,所以这里也是以url的形式来指定的-->
	<mapper url="file:///var/mappers/AuthorMapper.xml"/>
	<mapper url="file:///var/mappers/BlogMapper.xml"/>
	<mapper url="file:///var/mappers/PostMapper.xml"/>
</mappers>
<!--使用接口的全路径名指定,是接口,所以此时使用. 而不是/ -->
<mappers>
	<mapper class="org.mybatis.builder.AuthorMapper"/>
	<mapper class="org.mybatis.builder.BlogMapper"/>
	<mapper class="org.mybatis.builder.PostMapper"/>
</mappers>

<!--★-->
<!--使用包名指定,包含指定包下所有的接口,必须有接口才行-->
<mappers>
	<package name="org.mybatis.builder"/>
</mappers>

推荐使用第四种方式,但是前提是一定要显示的提供响应的接口定义,且要求接口名和mapper.xml文件名必须完全相同。

将易变内容写到配置文件中并用${var_name}的形式引入到mybatis配置文件

首先创建db.properties:

driver=com.mysql.cj.jdbc.Driver
url=jdbc:mysql://localhost:3306/db_name?serverTimezone=UTC
username=root
password=root

然后用<properties>标签将db.properties引入mybatis.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">
<configuration>
    <!--引入db.properties-->
    <properties resource="db.properties"></properties>
    <!--开启log4j的日志打印。默认就是开启的,可以不配置-->
    <settings>
        <!--当然,这个value除了是LOG4J还可以是SLF4J-->
    	<setting name="logImpl" value="LOG4J"/>
    </settings>
    ...
</configuration>

Resouces(注意是mybatis的Resouces)

可以直接写文件名而不用写文件绝对路径来获取文件流

// 获取mybatis配置文件的流对象
InputStream is = Resources.getResouceAsStream("file_name"); // 自动从当前项目的编译目录下获取配置文件的流对象

SqlSessionFactory与SqlSession

请结合下面的mapper配置文件

// 获取mybatis配置文件的流对象
InputStream is = Resources.getResourceAsStream("mybatis.xml"); // 自动从当前项目的编译目录下获取配置文件的流对象
// 获取SqlSessionFactory对象
SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(is);
// 获取SqlSession对象
SqlSession sqlSession = factory.openSession(); // 当SqlSession对象被创建成功后,已经连接好数据库了

//// 使用SQLSession对象完成数据库的单表查询操作
// List<Object> objects = sqlSession.selectList("SELECT * FROM tbl_name"); // sql语句如果写在这里,那耦合度就太高了,应该写到mapper中,如下:
// 查询所有
List<Emp> empList = sqlSession.selectList("com.bjsxt.mapper.EmpMapper.selEmp"); // "selEmp"表示mapper文件中该条sql语句的id。前缀:com.bjsxt.mapper.EmpMapper 表示mapper文件的namespace

// 根据id查询
Emp emp = sqlSession.selectOne("com.bjsxt.mapper.EmpMapper.selEmpById", 1);

// 多条件的单表查询
// 使用Map存储查询条件
HashMap<String, Object> hashMap = new HashMap<>();
hashMap.put("a", 0);
hashMap.put("b", 1);
List<Emp> emps = sqlSession.selectList("com.bjsxt.mapper.EmpMapper.selEmpBySqlAndDeptno", hashMap);
// 使用实体类对象存储查询条件
Emp emp1 = new Emp();
emp1.setSal(0.0);
emp1.setDeptno(1);
List<Emp> emps2 = sqlSession.selectList("com.bjsxt.mapper.EmpMapper.selEmpBySqlAndDeptno2", emp1);

//// 使用SqlSession对象完成数据库的单表新增、修改、删除操作
// 新增
// 创建Emp对象存储要增加的员工信息
Emp emp = new Emp();
emp.setEname("赵六");
emp.setDeptno(2);
emp.setSal(20000.00);
emp.setJob("程序员");
emp.setMgr(1);
int i = sqlSession.insert("com.bjsxt.mapper.EmpMapper.insEmp", emp);
// 提交事务
sqlSession.commit(); // 原生jdbc是自动提交事务的,当然也可以取消自动提交:conn.setAutoCommit(false); 但是mybatis必须手动提交
// 更新
Emp emp = new Emp();
emp.setEmpid(3);
emp.setEname("王五");
int i = sqlSession.update("com.bjsxt.mapper.EmpMapper.updateEmp", emp);
sqlSession.commit();
// 删除
int i = sqlSession.delete("com.bjsxt.mapper.EmpMapper.delEmp", 4);
sqlSession.commit();

使用mapper

// 假设有两个相关文件,一个是接口EmpMapper和一个是xml文件EmpMapper.xml
// 获取SqlSession对象之后,我们需要获取是哪个mapper
// 获取Mapper接口的实现类。注意,接口的实现类是在SqlSession对象被创建的时候即完成了生成,所以假设有5个接口,与之对应有5个xml文件,那么这5个接口实现会在SqlSession创建的时候就完成,到后面要用的时候直接取就行了
EmpMapper empMapper = sqlSession.getMapper(EmpMapper.class);
// 调用EmpMapper接口中的方法
List<Emp> emps = empMapper.selEmpMapper();

//// 有参数查询
// 一个形参
// 参数为基本类型
Emp emp = empMapper.selEmpById(1);
// 参数为引用类型
Emp emp1 = new Emp();
emp1.setEmpid(2);
Emp emp2 = empMapper.selEmpById2(emp1);
// 多个参数
// 参数全部为基本类型
List<Emp> emps = empMapper.selEmpBySalDept(2.0, 1);
// 参数全部为引用类型
Emp e1 = new Emp();
e1.setSal(3.0);
Emp e2 = new Emp();
e2.setDeptno(1);
List<Emp> emps2 = empMapper.selEmpBySalDept2(e1, e2);
// 参数为引用类型和基本类型的混用
Emp e3 = new Emp();
e3.setSal(4.0);
List<Emp> emps3 = empMapper.selEmpBySalDept3(e3, 2);

而且如果使用这种一个接口对应一个xml文件的方法的话,连parameterType都不用写了,因为可以直接把查询参数传到接口中去:

<!--EmpMapper.xml文件中的sql语句-->
<!--查询所有员工信息-->
<select id="selEmpMapper" resultType="com.bjsxt.pojo.Emp">
	select empid, ename, job, mgr from emp
</select>
<!--有参数的数据库操作-->
<!--根据Id查询员工信息-->
<!--参数为一个-->
<!--类型为基本类型:使用#{0}-->
<select id="selEmpById" resultType="com.bjsxt.pojo.Emp">
    <!--下面的#{0}中的0表示接口中形参的下标-->
	select empid, ename, job, mgr from emp from emp where empid=#{0}
</select>
<!--类型为引用类型:使用#{实体类的属性名}或者#{Map集合的键名(因为形参类型可能为Map)}-->
<select id="selEmpById2" resultType="com.bjsxt.pojo.Emp">
    <!--下面的#{empid中的empid表示接口中形参Emp的属性名empid-->
	select empid, ename, job, mgr from emp from emp where empid=#{empid}
</select>

<!--参数为多个-->
<!--参数全部为基本类型:根据工资和部门编号查询员工信息,使用#{param1},从接口方法从左至右开始,param1,param2,...-->
<select id="selEmpBySalDept" resultType="com.bjsxt.pojo.Emp">
    <!--下面的#{param2}表示形参中顺序数下去的第2个参数(注意,这里不让写#{1}了,应该写#{param2})-->
	select empid, ename, job, mgr from emp from emp where deptno=#{param2} and sal>#{param1}
</select>
<!--参数全部为引用类型:根据工资和部门编号查询员工信息,使用#{param1.属性名}或param1为Map集合时应该使用#{param1.键名}-->
<select id="selEmpBySalDept2" resultType="com.bjsxt.pojo.Emp">
    <!--下面的#{param2.deptno}表示第二个参数Emp的deptno属性-->
	select empid, ename, job, mgr from emp from emp where deptno=#{param2.deptno} and sal>#{param1.sal}
</select>
<!--引用类型和基本类型混用:根据工资和部门编号查询员工信息-->
<select id="selEmpBySalDept3" resultType="com.bjsxt.pojo.Emp">
	select empid, ename, job, mgr from emp from emp where deptno=#{param2} and sal>#{param1.sal}
</select>
public interface EmpMapper{
    // 查询所有的员工信息
    List<Emp> selEmpMapper();
    /**
    * 带有参数的数据库操作
    */
    //// 只有一个形参
    // 根据id查询员工信息:参数类型为基本类型
    Emp selEmpById(Integer empid);
    // 根据id查询员工信息:参数类型为引用类型
    Emp selEmpById2(Emp emp);
    
    //// 参数有多个
    // 参数全部为基本类型:根据工资和部门编号查询员工信息
    List<Emp> selEmpBySalDept(Double sql, Integer deptno);
    // 参数全部为引用类型:根据工资和部门编号查询员工信息
    List<Emp> selEmpBySalDept2(Emp e1, Emp e2);
    // 引用类型和基本类型混用:根据工资和部门编号查询员工信息
    List<Emp> selEmpBySalDept3(Emp e1, Integer deptno);
}

mapper配置文件

<?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文件的id-->
<mapper namespace="com.bjsxt.mapper.EmpMapper">
	<!--使用Mybatis完成单表的查询-->
    <!--
		声明查询所有的员工信息
			sql语句中没有参数
				- 使用resultType属性指明返回值存储的实体类的全限定路径
				- 如果查询结果是多条数据,使用selectList(String sqlPath)方法完成查询;如果只有一条,建议使用selectOne(String sqlPath)来完成查询
	-->
    <!--resultType表示使用哪个实体类去存数据库返回的信息-->
    <select id="selEmp" resultType="com.bjxst.pojo.Emp">
    	select * from emp
    </select>
    <!--
		根据ID查询员工信息:
			SQL语句中如果只有一个参数:
				- 使用parameterType属性指明参数的类型
				- 在SQL语句中使用#{任意字符}完成参数的占位
				- 如果查询结果是多条数据,使用selectList(String sqlPath, Object Param)方法完成查询;如果只有一条,建议使用selectOne(String sqlPath, Object Param)来完成查询
	-->
    <select id="selEmpById" resultType="com.bjsxt.pojo.Emp" parameterType="int">
    	select * from emp where empid=#{myempid}
    </select>
    
    <!--
		根据部门和工资查询员工信息
			sql语句中有多个参数的单表查询:
				- 使用parameterType属性指明参数的类型
					注意:parameterType属性表明的是Mybatis接受的实参的类型,不是SQL语句中的参数类型
				- 在SQL语句中使用#{任意字符}完成参数的占位
					注意:
						因为在Mybatis底层会自动给SQL语句中的占位符进行赋值,由我们自己赋值变为自动
						但是我们需要告诉Mybatis哪个值赋值给哪个占位符。所以Mybatis中的SQL语句中使用#{键名}的方式来完成占位以及赋值
				- 如果查询结果是多条数据,使用selectList(String sqlPath, Object Param)方法完成查询;如果只有一条,建议使用selectOne(String sqlPath, Object Param)来完成查询
	-->
    <!--parameterType="map"表示使用Map集合封装实参-->
    <select id="selEmpBySqlAndDeptno" resultType="com.bjsxt.pojo.Emp" parameterType="map">
    	select * from emp where sql > #{keyA} and deptno = #{keyB}
    </select>
    
    <!--使用实体类对象封装实参,占位使用#{属性名}-->
    <select id="selEmpBySqlAndDeptno2" parameterType="com.bjsxt.pojo.Emp" resultType="com.bjsxt.pojo.Emp">
    	select * from emp where sql > #{sql} and deptno = #{deptno}
    </select>
</mapper>

增、删、改

<?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文件的id-->
<mapper namespace="com.bjsxt.mapper.EmpMapper">
	<!--Mybatis的增、删、改,无需使用resultType属性表明返回值类型,默认为int-->
    <!--新增-->
    <insert id="insEmp" parameterType="com.bjsxt.pojo.Emp">
    	insert into emp values(default, #{ename}, #{job}, #{mgr}, now(), #{sal}, #{comm}, #{deptno})
    </insert>
    <!--更新-->
    <update id="updateEmp" parameterType="com.bjsxt.pojo.Emp">
    	update emp set ename=#{ename}, where empid=#{empid}
    </update>
    <!--删除-->
    <delete id="delEmp" parameterType="int">
    	delete from emp where empid=#{myempid}
    </delete>
</mapper>

日志

Mybatis的内置日志工厂(LogFactory)提供日志功能,内置日志工厂将日志交给以下其中一种工具作代理:

  • SLF4J
  • Apache Commons Logging
  • Log4j 2
  • Log4j
  • JDK logging
  • NO_LOGGING

Mybatis内置日志工厂基于运行时自省机制选择合适的日志工具。他会使用第一个查找得到的工具(按上面列举的顺序查找)。如果一个都没找到,日志功能就会被禁用。也就是说在项目中把日志工具环境配置出来后,不用在mybatis进行配置就可以让日志生效

不少应用服务器(如Tomcat和WebShpere)的类路径种已经包含Commons Logging,所以在这种配置环境下的Mybatis会把它作为日志工具,记住这点非常重要,这意味着,在诸如WebSphere的环境中,它提供了Commons Logging的私有实现,你的Log4J配置将被忽略。mybatis将你的Log4J配置忽略掉是相当郁闷的(事实上,正是因为在这种配置环境下,Mybatis才会选择使用Commons Logging而不是Log4J)。如果你的应用部署在一个了路径已经包含Commons Logging的环境中,而你又想使用其它日志工具,可以通过在mybatis配置文件mybatis-config.xml里面添加一项setting来选择别的日志工具:

<settings>
	<setting name="logImpl" value="LOG4J"/>
</settings>

简单来说,要在mybatis.cfg.xml中配置mybatis所使用的具体日志实现。如果不指定将自动搜索,可能会搜到log4j,但是也有可能会优先搜索到其他的日志实现,所以还是设置一下,让它匹配到log4j:

<settings>
	<setting name="logImpl" value="LOG4J"/>
</settings>

无论使用哪种日志工具对于我们来说目的都是一样的:打印运行过程中日志信息。日志信息中对平时开发最重要的是运行过程中SQL的打印,这也是在开发过程中mybatis日志的重要性

log4j配置文件

可以将全局的日志级别调高,避免大量debug信息的干扰,同时将对映射文件的操作调低,可以用来显示SQL语句的调试信息。开发阶段,建议启动控制的日志

#定义全局日志级别
log4j.rootLogger=error,stdout,logfile
#包级别日志。这句话必须有,因为我们要打印的日志是mapper包下的东西
log4j.logger.com.bjsxt.mapper=debug
#接口级别日志
#log4j.logger.com.bjsxt.mapper.EmployeeMapper=debug
#方法级别日志
#log4j.logger.com.bjsxt.mapper.EmployeeMapper.findById=debug
log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.Target=System.err
log4j.appender.stdout.layout=org.apache.log4j.SimpleLayout

log4j.appender.logfile=org.apache.log4j.FileAppender
log4j.appender.logfile.File=d:/bjsxt.log
log4j.appender.logfile.layout=org.apache.log4j.PatternLayout
log4j.appender.logfile.layout.ConversionPattern=%d{yyyy-MM-dd HH:mm:ss} %l %F %p %m%n

mybatis依赖的包

image-20210411182313174

生命周期

SqlSessionFactoryBuilder:

该类用来创建SqlSessionFactory对象,当SqlSessionFactory对象被创建后,该对象也就没有存在的必要了。

SqlSessionFactory:

该对象应该在你的应用执行期间一直存在,由于要从该对象中获取SqlSession对象,这样的操作会相当频繁,同时创建SqlSessionFactory对象是一件很耗费资源的事,因此,该对象的生命周期应该为应用返回,即与当前应用具有相同的生命周期。

SqlSession:

每个线程都应该有自己的SqlSession实例,SqlSession实力不能被共享,是线程不安全的,因此最佳的范围是请求或方法范围。

Mapper:

关闭SqlSession的时候也就关闭了由其所产生的Mapper

类型别名

别名 映射的类型
_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
object Object
map Map
hashmap HashMap
list List
arraylist ArrayList
collection Collection
iterator Iterator

有了类型别名我们就可以直接这么写了:

<!--注意下面的parameterType-->
<delete id="delEmp" parameterType="int">
    delete from emp where empid=#{myempid}
</delete>

include标签和sql标签提取公共SQL脚本片段

<!--
	问题:
		我们发现在Mapper.xml文件中不同的sql语句中会存在相同的sql片段,如果每次我们都自己声明,会造成代码维护困难
	解决:
		将相同的sql脚本片段抽取出来,在外部声明,然后在sql标签中调用即可
	实现:
		sql标签:声明公共的sql脚本片段
		include标签:引入公共的sql脚本片段
-->
<sql id="empSql">
	empid,ename,job,mgr
</sql>
<!--查询所有的员工信息-->
<select id="selEmpMapper" resultType="com.bjsxt.pojo.Emp">
	select <include refid="empSql"></include> from emp 
</select>

@Param

@Param是MyBatis所提供的(org.apache.ibatis.annotations.Param),作为Dao层的注解,作用是用于传递参数,从而可以与SQL中的的字段名相对应,一般在2=<参数数<=5时使用最佳。

@Param解决了参数可读性差的问题

先来看看原始的方法:

当只有一个参数时,没什么好说的,传进去一个值也只有一个参数可以匹配。当存在多个参数时,传进去的值就区分不开了,这时可以考虑用Map,例如接口:

public List<Role> findRoleByMap(Map<String, Object> parameter);

此时的xml文件可以这么写:

<select id="findRoleByMap" parameterType="map" resultType="role">
    SELECT id,name FROM t_role
    WHERE roleName=#{roleName}
    AND note=#{note}
<select>

测试文件:

RoleMapper roleMapper = sqlSession.getMapper(RoleMapper.class);
Map<String, Object> parameter = new HashMap<>();
parameter.put("roleName", "剑士");
parameter.put("note", "决战紫禁之巅");
List<Role> roles = roleMapper.findRolesByMap(parameter);

很明显上面的缺点就在于可读性差,每次必须阅读他的键,才能明白其中的作用,并且不能限定其传递的数据类型,下面是使用@Param的情况,需要将接口改为:

public List<Role> findRoleByAnnotation(@Param("roleName") String roleName, @Param("note") String note);

这样我们就可以直接传入对应的值了。

当然也可以使用Java Bean来传递多个参数,定义一个POJO:

public class RoleParam {
    private String roleName;
    private String note;
    /*getter和setter*/
}

此时接口就变为:

public List<Role> findRoleByBean(RoleParam role);

这样对应的xml文件与原始的方法的区别就在于id和parameterType发生了变化,id对应的方法和parameterType对应该类的全限定名。

而使用更多的场景可能是这样的,对应多个POJO:

public List<Role> findRoleByMix(@Param("roleP") RoleParam role, @Param("permissionP") PermissionParam permission);

这样就可以进行如下映射:

<select id="findRoleByMix" resultType="role">
    SELECT id,name FROM t_role
    WHERE roleName=#{roleP.roleName}
    AND note=#{rolep.note}
    AND level=#{permissionP.level}
<select>

注意:此时并不需要写出parameterType属性,Mybatis会进行自动搜索(这点在上面“使用mapper”这一章节中解释了)。

@MapKey

参考:https://blog.csdn.net/u012734441/article/details/85861337

Mybatis中if else的使用

参考:https://blog.csdn.net/qq_27327261/article/details/112470640

使用#{}占位的方式和使用${}占位的方式

现在有一个问题:

目前我们在使用mybatis完成数据库操作的时候,mybatis底层默认使用PreparedStatement对象完成数据库操作,也就是说Sql语句的赋值都是通过占位赋值的,那么如果我们想通过字符串拼接的方式赋值怎么办?也就是底层使用Statement对象完成数据库操作。

解决:

#{}方式占位:底层使用PreparedStatement对象完成数据库操作,sql语句本质上仍然是?占位赋值的
${}方式占位:底层使用Statement对象完成数据库操作,本质为sql语句的拼接

举例说明:

首先来看使用#{}占位方式:

mapper.xml文件:

<select id="selPs" resultType="com.bjsxt.pojo.Emp">
	select * from emp where ename=#{ename}
</select>

mapper接口中的方法:

// #{}的占位
List<Emp> selPs(@Param("ename") String ename);

测试文件:

EmpMapper mapper = sqlSession.getMapper(EmpMapper.class);
List<Emp> emps = mapper.selPs("张三"); // 此时底层使用PreparedStatement

使用${}占位方式:

mapper.xml文件:

<select id="selPs" resultType="com.bjsxt.pojo.Emp">
	select * from emp where ename='${ename}'
</select>

mapper接口中的方法:

// ${}的占位
List<Emp> selPs(@Param("ename") String ename);

测试文件:

EmpMapper mapper = sqlSession.getMapper(EmpMapper.class);
List<Emp> emps = mapper.selPs("张三"); // 此时底层使用Statement

模糊查询

在进行模糊查询时,在映射文件中可以使用concat()函数来连接参数和通配符。另外,注意对于特殊字符,比如<,不能直接书写,应该使用字符实体(诸如&lt;等)替换。

mapper接口方法:

// 模糊查询:根据员工名查询员工信息
List<Emp> selEmpByName(@Param("ename")String ename);

mapper.xml文件:

<!--模糊查询:根据员工姓名获取员工信息-->
<select id="selEmpByName" resultType="com.bjsxt.pojo.Emp">
    <!--下面的这个concat其实就是MySQL自带的函数,用来拼接字符串-->
	select * from emp where ename like concat('%', #{ename}, '%')
</select>

测试文件:

EmpMapper mapper = sqlSession.getMapper(EmpMapper.class);
List<Emp> emps = mapper.selEmpByName("三"); 

小于或者大于判断

mapper接口方法:

// 小于判断
List<Emp> selEmpBySal(@Param("sal") Double sal);

mapper.xml文件:

<!--小于判断-->
<select id="selEmpBySal" resultType="com.bjsxt.pojo.Emp">
	select * from emp where sal &lt; #{sal}
</select>

测试文件:

EmpMapper mapper = sqlSession.getMapper(EmpMapper.class);
List<Emp> emps = mapper.selEmpBySal(4000.0); 

分页查询

mapper接口方法:

List<Emp> selEmpByPage(@Param("ename") String ename, @Param("pageStart")int pageStart, @Param("pageSize")int pageSize);

mapper.xml文件:

<!--分页查询-->
<select id="selEmpByPage" resultType="com.bjsxt.pojo.Emp">
	select * from emp where sal &lt; #{sal} limit #{pageStart},#{pageSize}
</select>

测试文件:

// 分页查询
EmpMapper mapper = sqlSession.getMapper(EmpMapper.class);
List<Emp> emps = mapper.selEmpByPage(4000.0, 0, 5); 

自增主键回填

Mysql支持主键自增。有时候完成添加后需要立刻获取刚刚自增的主键,由下一个操作来使用。比如结算购物车之后,主订单的主键确定后,需要作为后续订单明细项的外键存在。对于主键的使用,我们可以用UUID(先手动生成一个UUID,再把它作为主键,为什么用UUID呢?因为UUID可以保证不重复),也可以让它自动生成,那么自动生成的时候我们如何拿到主键呢?Mybatis提供了支持,可以非常简单的获取。

方式1:通过useGeneratedKeys属性实现

<insert id="save" useGeneratedKeys="true" keyProperty="empno">
	insert into emp values(null,#{ename},#{job},#{mgr},#{hireDate},#{sal},#{comm},#{deptno})
</insert>

useGeneratedKeys:表示要使用自增的主键

keyProperty:表示把自增的主键赋给JavaBean的哪个成员变量

以添加Employee对象为例,添加前Employee对象的empno是空的,添加完毕后可以通过getEmpno()获取自增的主键。

方式2:通过selectKey元素实现

<insert id="save">
	<selectKey order="AFTER" keyProperty="empno" resultType="int">
		<!--SELECT LAST_INSERT_ID()-->
        <!--或者可以这么写:-->
        SELECT @@identity
    </selectKey>
    insert into emp(empno,ename,sal)values(null,#{ename},#{sal})
</insert>

order:取值AFTER|BEFORE,表示在新增之后|之前执行<selectKey>中的命令(由此可见这种方式的功能更加丰富)

keyProperty:表示将查询到的数据绑定到哪个属性上

动态SQL查询以及更新

if标签

接口方法:

// 根据员工的工作,部门,工资动态查询员工信息 ---if标签的使用
List<Emp> findEmp(@Param("job") String job,
                 @Param("deptno") Integer deptno,
                 @Param("sal") Double sal);

mapper.xml文件:

<!--
	根据员工的工作、部门、工资动态查询员工信息
	使用if标签完成逻辑判定,完成SQL语句的拼接
	使用:
		在SQL标签中声明
	属性:
		test:值为判断的逻辑条件,直接使用@Param注解中声明的名字即可获取实参。不同的条件使用 and or 关键字连接
-->
<select id="findEmp" resultType="com.bjsxt.pojo.Emp">
	select * from emp where 1=1
    <if test="job!=null and job!=''">
    	and job=#{job}
    </if>
    <if test="deptno!=null and deptno!=''">
    	and deptno=#{deptno}
    </if>
    <if test="sal!=null and sal!=''">
    	and sal > #{sal}
    </if>
</select>

测试文件:

EmpMapper mapper = sqlSession.getMapper(EmpMapper.class);
// 动态SQL查询
List<Emp> emps = empMapper.findEmp(null, 1, null);

如果是集合判空,则应该这么写:

<if test="collection!=null and collection.size()!=0">
</if>

where标签

接口方法:

// 根据员工的工作,部门,工资动态查询员工信息 ---where标签的使用
List<Emp> findEmp(@Param("job") String job,
                 @Param("deptno") Integer deptno,
                 @Param("sal") Double sal);

mapper.xml文件:

<!--
	根据员工的工作、部门、工资动态查询员工信息
	where标签的使用:
		作用:相当于声明了where关键字
		注意:
			只有当条件成立时才会生成where关键字,并且会自动忽略第一个and关键字
			因为无法预知哪个条件会成立,所以建议每个条件前都声明SQL语句的条件链接符号(and|or)

-->
<select id="findEmp" resultType="com.bjsxt.pojo.Emp">
	select * from emp
    <where>
    	<if test="job!=null and job!=''">
    		and job=#{job}
        </if>
        <if test="deptno!=null and deptno!=''">
            and deptno=#{deptno}
        </if>
        <if test="sal!=null and sal!=''">
            and sal > #{sal}
        </if>
    </where>
</select>

测试文件:

EmpMapper mapper = sqlSession.getMapper(EmpMapper.class);
// 动态SQL查询
List<Emp> emps = empMapper.findEmp(null, 1, null);

bind标签

接口方法:

// 根据员工的工作,部门,工资动态查询员工信息 ---bind标签的使用
List<Emp> findEmp(@Param("job") String job,
                 @Param("deptno") Integer deptno,
                 @Param("sal") Double sal);

mapper.xml文件:

<!--
	根据员工的工作、部门、工资动态查询员工信息
	bind标签的使用:
		作用:将接收到的实参进行进一步的修饰后传递给SQL语句使用
		使用:
			<bind name="新的键名" value="原始数据的名字+'拼接的新数据'"/>
			一般在模糊查询的时候使用(拼接%)
-->
<select id="findEmp" resultType="com.bjsxt.pojo.Emp">
	<bind name="job_alias" value="job+'qq1'"/>
    select * from emp where job=#{job_alias} and deptno=#{deptno} and sal=#{sal}
</select>

测试文件:

EmpMapper mapper = sqlSession.getMapper(EmpMapper.class);
// 动态SQL查询
List<Emp> emps = empMapper.findEmp("讲师", 1, 10000.0);

set标签

接口方法:

// set标签的使用
int updateEmp(Emp emp);

mapper.xml文件:

<!--
	set标签的作用:
		set标签用在update语句中给字段赋值。借助if的配置,可以只对有具体值的字段进行更新。set元素会自动帮助添加set关键字,自动去掉最后一个if语句的多余的逗号
-->
<update id="updateEmp">
	update emp
    <set>
    	<if test="ename!=null and ename !=''">
        	ename=#{ename},
        </if>
        <if test="job!=null and job !=''">
        	job=#{job},
        </if>
        <if test="mgr!=null and mgr !=''">
        	mgr=#{mgr},
        </if>
        <if test="sal!=null and sal !=''">
        	sal=#{sal},
        </if>
        <!--写下面这一句的原因是:如果一个要更新的字段都没有,那么就会生成这样的sql语句:update emp where id=?,显然是会报错的,为了防止这种情况,更新一下empid即可(empid是一定存在的),这样的话就算一个要更新的字段都没,也会生成这样的sql语句:update emp set empid=? where empid=?,这样就不会报错了-->
        empid=#{empid},
    </set>
</update>

测试文件:

EmpMapper mapper = sqlSession.getMapper(EmpMapper.class);
// set标签
Emp emp = new Emp();
emp.setEmpId(1);
emp.setEmpName("qyf");
int i = empMapper.updateEmp(emp);

foreach标签

接口方法:

List<Emp> findEmpByIds(List<Integer> ids);

mapper.xml文件:

<!--
	foreach标签的作用:
		foreach标签是非常强大的,它允许你指定一个集合或数组,声明集合项和索引变量,他们可以用在标签内。它也允许你指定开放和关闭的字符串,在迭代之间放置分隔符。这个元素是很智能的,它不会偶然地附加多余的分隔符
		注意:你可以传递一个List实例或者数组作为参数对象传给mybatis。当你这么做的时候,mybatis会自动将它包装在一个Map中,用名称在作为键。List实例将会以“list”作为键,而数组实例将会以“array”作为键
		特别注意:在进行sql优化时有一点就是建议少用in语句,因为对性能有影响。如果in中元素很多的话,会对性能有较大影响,此时就不建议使用foreach语句了
-->
<select id="findEmpByIds" resultType="com.bjsxt.pojo.Emp">
	select * from emp where empid in
    <foreach collection="list" open="(" close=")" separator="," item="id">
    	#{id}
    </foreach>
</select>

当然也可以配合@Param使用:

接口方法:

List<Emp> findEmpByIds(@Param("ids") List<Integer> ids);

mapper.xml文件:

<select id="findEmpByIds" resultType="com.bjsxt.pojo.Emp">
	select * from emp where empid in
    <foreach collection="ids" open="(" close=")" separator="," item="id">
    	#{id}
    </foreach>
</select>

测试文件:

EmpMapper mapper = sqlSession.getMapper(EmpMapper.class);
List<Integer> ids = new ArrayList<>();
ids.add(1);
ids.add(2);
List<Emp> emps = empMapper.findEmpByIds(ids);

如果有这么一个需求:需要联合ids和ename进行查询,ename使用模糊查询。那么可以这么写:

接口:

List<Emp> findEmpByNameAndIds(@Param("ename")String ename, @Param("ids") List<Integer> ids);

mapper.xml文件:

<select id="findEmpByNameAndIds" resultType="com.bjsxt.pojo.Emp">
	select * from emp where
    ename like concat('%', #{ename}, '%')
    and
    empid in
    <foreach collection="ids" open="(" close=")" separator="," item="id">
    	#{id}
    </foreach>
</select>

测试文件:

EmpMapper mapper = sqlSession.getMapper(EmpMapper.class);
List<Integer> ids = new ArrayList<>();
ids.add(1);
ids.add(2);
List<Emp> emps = empMapper.findEmpByNameAndIds("张三", ids);

多表查询

无级联查询

开发者手动完成多表数据组装,需执行多条sql语句

接口方法:

EmpMapper.java:

// 多对一:查询员工及其部门信息
List<Emp> selEmpInfoMapper();

// 一对多:查询部门及其员工信息
List<Emp> selEmpInfoByDeptnoMapper(@Param("deptno")Integer deptno);

DeptMapper.java:

// 多对一:查询员工及其部门信息
// 根据部门编号获取部门信息
Dept selDeptByIdMapper(@Param("deptno")Integer deptno);

// 一对多:查询部门及其员工信息
List<Dept> selDeptInfoMapper();

EmpMapper.xml文件:

<!--
	多对一:查询员工及其部门信息
-->
<select id="selEmpInfoMapper" resultType="com.bjsxt.pojo.Emp">
	select * from emp
</select>

<!--
	一对多:查询部门及其员工信息
-->
<select id="selEmpInfoByDeptnoMapper" resultType="com.bjsxt.pojo.Dept">
	select * from emp where deptno=#{deptno}
</select>

DeptMapper.xml文件:

<!--
	多对一:查询员工及其部门信息
	根据部门编号获取部门信息
-->
<select id="selDeptByIdMapper" resultType="com.bjsxt.pojo.Dept">
	select * from dept where deptno=#{deptno}
</select>

<!--
	一对多:查询部门及其员工信息
-->
<select id="selDeptInfoMapper" resultType="com.bjsxt.pojo.Dept">
	select * from dept
</select>

测试文件(多对一:查询员工及其部门信息):

// 多对一:查询员工及其部门信息
// 获取员工mapper
EmpMapper empMapper = sqlSession.getMapper(EmpMapper.class);
// 获取部门mapper
DeptMapper deptMapper = sqlSession.getMapper(DeptMapper.class);
// 1、查询所有的员工信息
List<Emp> emps = empMapper.selEmpInfoMapper();
// 2、遍历结果,根据员工的部门编号获取部门信息,并存储到对应的员工对象中
for(Emp emp: emps){
    // 根据员工的部门编号获取部门信息
    Dept dept = deptMapper.selDeptByIdMapper(emp.getDeptno());
    emp.setDept(dept);
}

测试文件(一对多:查询部门及其员工信息):

// 一对多:查询部门及其员工信息
// 获取员工mapper
EmpMapper empMapper = sqlSession.getMapper(EmpMapper.class);
// 获取部门mapper
DeptMapper deptMapper = sqlSession.getMapper(DeptMapper.class);
// 1、查询所有的部门信息
List<Dept> depts = deptMapper.selDeptInfoMapper();
// 2、循环查询部门的员工信息,并将结果存储到对应的部门对象中
for(Dept dept: depts){
    // 根据部门编号获取员工信息
    List<Emp> emps = empMapper.selEmpInfoByDeptnoMapper(dept.getDeptno());
    // 将结果存储到部门对象中
    dept.setEmpList(emps);
}

级联查询

使用mybatis映射配置自动完成数据的组装,需执行多条sql语句

接口方法:

EmpMapper.java:

// 级联查询
// 多对一:查询员工及其部门信息
List<Emp> selEmpInfoMapper();

DeptMapper.java:

// 级联查询
// 一对多:查询部门及其员工信息
List<Dept> selDeptInfoMapper();

EmpMapper.xml文件:

<!--
	级联查询
	多对一:查询员工及其部门信息
-->
<resultMap id="rm2" type="com.bjsxt.pojo.Emp">
	<result property="ename2" column="ename"></result>
	<result property="job2" column="job"></result>
    <!--注意:我们说resultMap中属性和字段一致的就可以省略不写,但是级联查询中很有可能出现两个表存在公共同名字段,那么两个表中的公共的字段不能省略,否则查出来的结果中该公共字段就会为null(原因是mybatis底层在将部门信息赋值到员工的时候会通过员工表的deptno去查询部门信息,这个时候相当于deptno就已经被用过一次了,那么它就不会被使用第二次,导致就不会再给员工信息表中的deptno赋值了,导致了查出来的结果中deptno为null)-->
    <!--这个地方我们写清楚了公共字段deptno的映射,mybatis底层就一定会给deptno赋值-->
    <result property="deptno" column="deptno"></result>
	<!--mybatis实现级联查询的奥妙在这-->
    <!--这里的column表示的意义就不一样了,它的作用是告诉select使用deptno的值去查;同时property表示的意义也不一样了,它的作用是select查出来的东西赋值给实体的dept属性-->
    <association property="dept" column="deptno" select="com.bjsxt.mapper.DeptMapper.selDeptByIdMapper"></association>
</resultMap>
<select id="selEmpInfoMapper" resultMap="rm2">
	select * from emp
</select>

DeptMapper.xml文件:

<!--
	级联查询
	一对多:查询部门及其员工信息
-->
<resultMap id="rm" type="com.bjsxt.pojo.Dept">
    <id property="deptno" column="deptno"></id>
	<!--使用collection标签来表明员工信息的获取-->
    <!--上面多对一的时候我们用了association标签,这里用了collection标签,怎么辨别到底用什么标签呢?很简单,多对一中一个实体对应一个实体,因此结果只可能是一个,所以用association;一对多中一个实体对应多个实体,因此结果可能有多个,所以用collection-->
    <!--下面的ofType告诉mybatis对于empList的查询结果的类型是什么-->
    <!--这里的column表示的意义就不一样了,它的作用是告诉select使用deptno的值去查;同时property表示的意义也不一样了,它的作用是select查出来的东西赋值给实体的empList属性-->
    <collection property="empList" ofType="com.bjsxt.pojo.Emp" column="deptno" select="com.bjsxt.mapper.EmpMapper.selEmpInfoByDeptnoMapper"></collection>
</resultMap>
<select id="selDeptInfoMapper" resultMap="rm">
	select * from dept
</select>

测试文件:

// 级联查询
// 多对一:查询员工及其部门信息
EmpMapper mapper = sqlSession.getMapper(EmpMapper.class);
List<Emp> emps = mapper.selEmpInfoMapper();
// 一对多:查询部门及其员工信息
DeptMapper mapper2 = sqlSession.getMapper(DeptMapper.class);
// 查询
List<Dept> depts = mapper2.selDeptInfoMapper(); // 注意:由于使用mybatis的级联查询,对于Dept实体中的List<Emp> empList中的Emp的查询,mybatis是按照属性字段名一致为前提去查询的,所以一旦属性字段名不一致,mybatis查出来的Emp中的某些属性字段名不一致的属性将会为null

延迟加载

认识N + 1(或者说1 + N)问题

示例1:查询所有员工信息(含部门名称)

select * from emp;
select * from dept where deptno = 20;
select * from dept where deptno = 30;
select * from dept where deptno = 30;

示例2:查询所有部门信息(含每个部门的员工信息)

select * from dept;
select * from emp where deptno = 10;
select * from emp where deptno = 20;
select * from emp where deptno = 30;
select * from emp where deptno = 40;

如果第一个查询有N条记录,随后对数据库进行N此查询,共计1+N次查询,对数据库查询次数多,服务器亚历山大,成为N+1问题。

如何解决呢?

  • 延迟加载

    关联表的数据只有等到真正使用的时候才进行查询。不使用不查询。多用在关联对象或集合中。

    比如功能:查询所有员工的信息中只显示员工信息,而不显示部门信息;那对部门的sql语句不就白查询了吗,能否默认不查询,等真正使用的时候才查询,延迟一下查询的时间,这不就灵活了吗?

    resultMap可以实现高级映射(使用 association、collection实现一对一及一对多映射),association、collection具备延迟加载设置功能。

    延迟加载的好处:先从单表查询、需要时再从关联表去关联查询,大大提高数据库性能,因为查询单表要比关联查询多张表速度要快

  • 连接查询

    一条SQL语句查询到所有的数据。

延迟加载的设置

方式一:全局开关:在mybatis.xml配置文件中打开延迟加载的开关。配置完成后所有的association和collection元素都生效

<settings>
    <!--lazyLoadingEnabled表示是否开启延迟加载。是mybatis是否启用懒加载的全局开关。当开启时,所有关联对象都会延迟加载。特定关联关系中可通过设置fetchType属性来覆盖该项的开关状态-->
	<seting name="lazyLoadingEnabled" value="true"/>
	<!--aggressiveLazyLoading开启时,任何方法的调用都会加载懒加载对象的所有属性。否则,每个属性会按需加载-->
    <seting name="aggressiveLazyLoading" value="true"/>
</settings>

方式二:分开关:指定的association和collection元素中配置fetchType属性。eager:表示立刻加载;lazy:表示延迟加载。将会覆盖全局延迟设置。

多表联合查询(连接查询)

使用mybatis映射配置自动完成数据组装,只需执行一条sql语句

接口方法:

EmpMapper.java:

// 多对一:查询员工及其部门信息
List<Emp> selEmpInfoMapper();

DeptMapper.java:

// 一对多:查询部门及其员工信息
List<Emp> selDeptInfoMapper();

EmpMapper.xml文件:

<!--联合查询方式-->
<!--多对一:查询员工及其部门信息-->
<resultMap id="rm3" type="com.bjsxt.pojo.Emp">
	<!--联合查询的时候属性字段映射不能省略!-->
    <id property="empid" column="empid"></id>
    <result property="ename" column="ename"></result>
    <result property="job" column="job"></result>
    <result property="mgr" column="mgr"></result>
    <result property="deptno" column="deptno"></result>
	<!--下面的association标签不用写column了,因为sql语句已经把数据都查出来了,但是需要写javaType指定property的数据的类型-->
    <association property="dept" javaType="com.bjsxt.pojo.Dept">
		<!--联合查询的时候属性字段映射不能省略!-->
    	<id property="deptno" column="deptno"></id>
        <result property="dname" column="dname"></result>
        <result property="ddesc" column="ddesc"></result>
    </association>
</resultMap>
<select id="selEmpInfoMapper" resultMap="rm3">
	select * from emp e
    join dept d
    on e.deptno=d.deptno
</select>

DeptMapper.xml文件:

<!--联合查询方式-->
<!--一对多:查询部门及其员工信息-->
<resultMap id="rm3" type="com.bjsxt.pojo.Dept">
	<!--联合查询的时候属性字段映射不能省略!-->
	<id property="deptno" column="deptno"></id>
    <result property="dname" column="dname"></result>
    <result property="ddesc" column="ddesc"></result>
    <collection property="empList" ofType="com.bjsxt.pojo.Emp">
		<!--联合查询的时候属性字段映射不能省略!-->
    	<id property="empid" column="empid"></id>
        <result property="ename" column="ename"></result>
        <result property="job" column="job"></result>
        <result property="mgr" column="mgr"></result>
        <result property="sal" column="sal"></result>
        <result property="comm" column="comm"></result>
        <result property="deptno" column="deptno"></result>
    </collection>
</resultMap>
<select id="selDeptInfoMapper" resultMap="rm3">
	select * from dept d
    join emp e
    on e.deptno=d.deptno
</select>

测试文件:

// 多表联合查询
// 多对一:查询员工及其部门信息
EmpMapper mapper = sqlSession.getMapper(EmpMapper.class);
List<Emp> emps = mapper.selEmpInfoMapper();

// 一对多:查询部门及其员工信息
DeptMapper mapper2 = sqlSession.getMapper(DeptMapper.class);
List<Dept> emps = mapper2.selDeptInfoMapper();

查询结果注入规则:自动注入(Auto-Mapping)与手动注入(ResultMap)

接口方法:

// 自动注入-Autowired
List<Emp> selEmpByAuto();
// 手动注入-ResultMap
List<Emp> selEmpByResultMap();

mapper.xml文件:

<!--注入规则-->
<!--
	自动注入:mybatis在完成查询后,会自动的按照实体类的属性名和查询结果的字段名一致的规则将数据注入到实体类对象中。
	注意:
		1、实体类的属性名必须和字段名一致
		2、在查询标签上使用resultType表明要注入的实体类的全限定路径
	
	手动注入:如果我们设计的存储查询结果的实体类的属性名和查询结果的字段名不一致,需要手动配置实体类和查询结果之间的映射关系
	使用:
		1、在查询标签上使用ResultMap属性引入声明的规则的ID
		2、使用resultMap标签表明映射关系
	注意:
		1、resultMap配置的映射关系中可以只写属性名和字段名不一致的那些字段
-->
<!--自动注入-->
<select id="selEmpByAuto" resultType="com.bjsxt.pojo.Emp">
	select * from emp
</select>

<!--手动注入-->
<!--使用resultMap标签声明查询结果和实体类之间的映射关系-->
<resultMap id="rm1" type="com.bjsxt.pojo.Emp">
	<id property="empid" column="empid"></id>
    <result property="ename2" column="ename"></result>
    <result property="job2" column="job"></result>
    <result property="mgr" column="mgr"></result>
    <result property="sal" column="sal"></result>
    <result property="deptno" column="deptno"></result>
</resultMap>
<select id="selEmpByResultMap" resultMap="rm1">
    select * from emp
</select>

测试文件:

EmpMapper mapper = sqlSession.getMapper(EmpMapper.class);
// 查询所有的员工信息(使用自动注入规则)
List<Emp> emps = empMapper.selEmpByAuto(); // 此时如果实体类Emp中有属性ename2,但是数据库中没有该字段(或者说数据库中该字段叫ename),那么此时查到的结果中实体Emp的属性ename2将会是null

// 查询所有的员工信息(使用手动注入规则)
List<Emp> emps = empMapper.selEmpByResultMap();

javaType

property的类型,一个完整的类名,或者是一个类型别名。如果你匹配的是一个JavaBean,那mybatis通常会自行检测到。

首先改属性可写可不写,因为mybatis底层通过反射就可以直接拿到数据的类型

简单来讲它的作用:column=”empid“,property=”empid“,我希望column的值转到property的值的时候能转成javaType的属性

用法示例:

<resultMap id="rm" type="com.bjsxt.pojo.Emp">
	<id property="empid" javaType="java.lang.Integer" column="empid"></id>
</resultMap>

jdbcType

column在数据库表中的类型。这个属性只在insert、update或delete的时候对允许空的列有用。JDBC需要这项,但Mybatis不需要。取值是JDBCType枚举的值

这个属性也是可写可不写的

用法示例:

<select id="selectByPrimaryKey" resultMap="BaseResultMap">
    select *
    from emp
    where id = #{id,jdbcType=INTEGER}
</select>

Mybatis配置联合主键

如果使用<resultType></resultType>标签,则直接指定Bean路径就行,不用管这个问题

如果使用<resultMap></resultMap>标签,则需要这样定义resultMap

<resultMap>
    <!-- 下面这两个字段就是联合主键 -->
   <id property="enterId" column="enter_id"/>
	<id property="sId" column="s_id"/>
</resultMap>

使用注解代替mapper.xml

注意:注解虽然可以完全替代mapper.xml文件,但是当写动态sql查询的时候注解会相当麻烦,此时最好还是用mapper.xml

接口方法:

// 查询所有的员工信息
@Select("select * from emp")
List<Emp> selEmpInfoMapper();
// 根据id获取员工信息
@Select("select * from emp where empid=#{empid}")
Emp selEmpByIdMapper(@Param("empid")Integer empid);
// 根据岗位和工资查询员工信息
@Select("select * from emp where job like concat('%',#{job},'%') and sal>#{sal}")
List<Emp> selEmpByJobSqlMapper(@Param("job")String job, @Param("sal")Double sal);
// 新增员工信息
@Insert("insert into emp values(default,#{ename},#{job},#{mgr},now(),#{sal},#{comm},#{deptno})")
int addEmpMapper(Emp emp);
// 更新员工信息
@Update("update emp set ename=#{ename} where empid=#{empid}")
int updateEmpMapper(@Param("ename")String ename, @Param("empid")String empid);
// 删除
@Delete("delete from emp where empid=#{empid}")
int delEmp(@Param("empid")Integer empid);

Mybatis运行原理

构建SqlSessionFactory

InputStream is = Resources.getResourceAsStream("mybatis.xml");
SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(is);

第一步:通过XMLConfigBuilder解析全局配置文件,读出参数,并将读取的内容存入Configuration对象中。Configuration采用单例模式,只有一份。

第二步:使用Configuration去创建SqlSessionFactory对象。SqlSessionFactory是一个接口,这里返回的是实现类DefaultSqlSessionFactory。此处使用的构建者(Builder)模式。对于复杂对象的创建,使用构造方法很难实现,可以使用构建者模式。

经过跟踪发现,最终映射文件的信息存储到了Configuration的mappedStatements成员变量中:

image-20210413205035791

这一块可以直接点SqlSessionFactory点进去看源码

注意:构建SqlSessionFactory的时候,不仅读取了主配置文件的所有信息(包括<mappers>),而且也读取了各个映射文件的信息,均放入了MyBatis相应的对象中。

创建SqlSession

SqlSession session = factory.openSession();

image-20210413205302461

SqlSession是一个接口,这里返回的是其实现类DefaultSqlSession的一个实例。其中有一个Executor类型的成员变量。其实进行数据库操作时SqlSession就是一个门面,真正干活的却是Executor。

  • Executor:执行器,由它来调度StatementHandler、ParameterHandler、ResultSetHandler等来执行对应的SQL,其中StatementHandler是最重要的。
    • StatementHandler:使用数据库的Statemet(PreparedStatement)执行操作
    • ParameterHandler:处理SQL参数
    • ResultSetHandler:处理结果集ResultSet的封装返回

创建Mapper代理对象并执行数据库CRUD操作

EmployeeMapper mapper = session.getMapper(EmployeeMapper.class);

底层使用了动态代理模式:

image-20210413205703902

使用Mapper代理方式访问数据库:

Employee emp = mapper.findAll();
Employee emp = mapper.findById(7839);
int n= mapper.insert(emp);

跟踪源码,发现其实还是调用了SqlSession的selectList()、selectOne()、update()等方法。具体查看org.apache.ibatis.binding.MapperMethod类:

image-20210413205904585

image-20210413205911984

使用SqlSession执行CRUD操作

session.selectList("com.bjsxt.mapper.EmployeeMapper.selectAll");
session.selectOne("com.bjsxt.mapper.EmployeeMapper.selectById", 7839);
session.update("com.bjsxt.mapper.EmployeeMapper.updateEmp", emp);

经过跟踪代码发现,SqlSession的selectOne()、selectMap()方法的底层都调用了selectList()方法,而insert()、delete()底层都调用了update()方法。因为研究selectList()、update()方法即可:

image-20210413210159674

执行过程中统领全局的代码都在Executor中,比如下图的doUpdate()是完成insert、update、delete操作的,而doQuery()是完成select操作的。

image-20210413210215262

image-20210413210227389

核心步骤就是四步:

  • 获取数据库连接
  • 调用StatementHandler的prepare()方法,创建Statement对象并设置其超时、获取的最大行数等。
  • 调用StatementHandler的parameterize()方法。负责将具体的参数传递给SQL语句。
  • 调用StatementHandler的query()/update方法。完成查询/DML操作,并对结果集进行封装处理,然后返回最终结果。
1.Connection connection = getConnection(statementLog);
2.stmt = handler.prepare(connection, transaction.getTimeout());
3.handler.parameterize(stmt);
4.handler.query(stmt, resultHandler);
//4.handler.update(stmt);

image-20210413210529919

第三步的parameterize(),负责将具体的参数传递给SQL语句。这其中用到了ParameterHandler的setParameters()和TypeHandler的setParameter()。

image-20210413210635142

image-20210413210714023

第四步的query(),完成查询操作,并对结果集进行对象的封装处理。具体实现细节在ResultSetHandler的handleResults()方法中。

image-20210413210800125

image-20210413210806649

mybatis缓存

SqlSession是由SqlSessionFactory创建的,一个用户对应一个SqlSession,用完之后直接销毁

所以SqlSession是一级缓存,SqlSessionFactory是二级缓存

查数据的时候首先查二级缓存,如果二级缓存中没有,就查一级缓存,如果一级缓存还没有再查数据库

可以想象,用户操作了一遍之后SqlSession中会缓存很多数据,那这些数据什么情况下会变二级缓存呢?

首先,mybatis要发现该数据可能同样会被别的SqlSession请求(换句话说该数据可能比较公共,而且可能会被频繁查询)。然后,要等该SqlSession被销毁之后,这些数据才会被存入二级缓存。

注意

  • 默认情况下,mybatis开启一级缓存,没有开启二级缓存,当数据量大的时候可以借助一些第三方缓存框架或Redis缓存来协助保存mybatis的二级缓存数据
  • mybatis的缓存功能终究还是附带的功能,当我们真正需要用到缓存的时候,还是直接上redis等更专业的缓存工具

一级/二级缓存

参考:

https://blog.csdn.net/apple_67445472/article/details/130620705(MyBatis一级缓存详细讲解)

https://blog.csdn.net/weixin_52851967/article/details/125190163(Mybatis 之 二级缓存)

开启二级缓存

那么如何开启二级缓存呢?

  • 全局开关

    在mybatis-config.xml文件中的<settings>标签配置开启二级缓存

    <settings>
      <setting name="cacheEnabled" value="true"/>
    </settings>
    

    cacheEnabled的默认值就是true,所以这步的设置可以忽略

  • 分开关

    在要开启二级缓存的mapper文件中开启缓存

    <mapper namespace="com.bjsxt.mapper.EmpMapper">
      <cache/>
    </mapper>
    
  • 缓存中存储的JavaBean对象必须实现序列化接口

    public class Emp implments Serializable{}
    

Mybatis拦截器

参考张润华的supcon-parent或者网络资料

Mybatis处理器

有诸如MetaObjectHandlerTypeHandlerEnumOrdinalTypeHandler等,可以参考张润华的supcon-parent或者网络资料

Mybatis动态数据源配置

参考博客:https://blog.csdn.net/lvdou1120101985/article/details/81184871(Mybatis配置多数据源)、https://www.cnblogs.com/xifengxiaoma/p/11040336.html(mybatis动态数据源配置)、https://www.cnblogs.com/zhaww/p/12706941.html(mybatis-plus动态数据源配置)

Mybatis兼容多种数据库(利用databaseId)

参考:https://blog.csdn.net/weixin_41549393/article/details/124493207(Mybatis 兼容多种数据库(利用databaseId))

Mybatis批量更新数据三种方法

  • case when
  • duplicate key update(最快)
  • for循环

参考:https://blog.csdn.net/q957967519/article/details/88669552

Mybatis代码生成器

Mybatis-Generator

Mybatis-Generator的运行方式有很多种:

  • 基于mybatis-generator-core-x.x.x.jar和其XML配置文件,通过命令行运行。
  • 通过AntTask结合其XML配置文件运行。
  • 通过Maven插件运行。
  • 通过Java代码和其XML配置文件运行。
  • 通过Java代码和编程式配置运行。
  • 通过Eclipse Feature运行。

这里只介绍通过Maven插件运行和通过Java代码和其XML配置文件运行这两种方式,两种方式有个特点:都要提前编写好XML配置文件。

先注意一点:默认的配置文件为ClassPath:generatorConfig.xml

XML配置文件详解

XML配置文件才是Mybatis-Generator的核心,它用于控制代码生成的所有行为。所有非标签独有的公共配置的Key可以在mybatis-generator-corePropertyRegistry类中找到。下面是一个相对完整的配置文件的模板:

<?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>

  <properties resource="db.properties"/>
  <!--mysql 连接数据库jar 这里选择自己本地位置-->
  <classPathEntry location="C:\Users\hgb\.m2\repository\mysql\mysql-connector-java\8.0.23\mysql-connector-java-8.0.23.jar" />

  <context id="mysqlTables" targetRuntime="MyBatis3Simple">

    <!--数据库连接的信息:驱动类、连接地址、用户名、密码 -->
    <jdbcConnection driverClass="com.mysql.cj.jdbc.Driver"
                    connectionURL="jdbc:mysql://localhost:3306/demo"
                    userId="root"
                    password="root">
    </jdbcConnection>

    <plugin type="org.mybatis.generator.plugins.SerializablePlugin"/>

    <commentGenerator>
        <property name="suppressDate" value="true"/>
        <!-- 是否去除自动生成的注释 true:是 : false:否 -->
        <property name="suppressAllComments" value="true"/>
    </commentGenerator>

    <!-- 默认false,把JDBC DECIMAL 和 NUMERIC 类型解析为 Integer,为 true时把JDBC DECIMAL 和
           NUMERIC 类型解析为java.math.BigDecimal -->
    <javaTypeResolver>
        <property name="forceBigDecimals" value="true"/>
        <property name="useJSR310Types" value="true"/>
    </javaTypeResolver>

    <!-- targetProject:生成PO类的位置 -->
    <javaModelGenerator targetPackage="com.deya.simple.futures.report.entity.po" targetProject="src/main/java">
      <property name="enableSubPackages" value="true" />
      <property name="trimStrings" value="true" />
    </javaModelGenerator>
    <!-- targetProject:mapper映射文件生成的位置
         如果maven工程只是单独的一个工程,targetProject="src/main/java"
         若果maven工程是分模块的工程,targetProject="所属模块的名称",例如:
         targetProject="ecps-manager-mapper",下同-->
    <sqlMapGenerator targetPackage="mapper"  targetProject="src/main/resources">
      <property name="enableSubPackages" value="true" />
    </sqlMapGenerator>

    <!-- targetPackage:mapper接口生成的位置 -->
    <javaClientGenerator type="XMLMAPPER" targetPackage="com.deya.simple.futures.report.dao"  targetProject="src/main/java">
      <property name="enableSubPackages" value="true" />
    </javaClientGenerator>

    <!-- 指定数据库表 -->
    <table schema="MYSQLADMIN" tableName="USER" domainObjectName="Customer" >
      <property name="useActualColumnNames" value="true"/>
      <generatedKey column="ID" sqlStatement="DB2" identity="true" />
      <columnOverride column="DATE_FIELD" property="startDate" />
      <ignoreColumn column="FRED" />
      <columnOverride column="LONG_VARCHAR_FIELD" jdbcType="VARCHAR" />
    </table>

  </context>
</generatorConfiguration>

配置文件中,最外层的标签为<generatorConfiguration>,它的子标签包括:

  • 0或者1个<properties>标签,用于指定全局配置文件,下面可以通过占位符的形式读取<properties>指定文件中的值。
  • 0或者N个<classPathEntry>标签,<classPathEntry>只有一个location属性,用于指定数据源驱动包(jar或者zip)的绝对路径,具体选择什么驱动包取决于连接什么类型的数据源。
  • 1或者N个<context>标签,用于运行时的解析模式和具体的代码生成行为,所以这个标签里面的配置是最重要的。

下面分别列举和分析一下<context>标签和它的主要子标签的一些属性配置和功能。

context标签

<context>标签在mybatis-generator-core中对应的实现类为org.mybatis.generator.config.Context,它除了大量的子标签配置之外,比较主要的属性是:

  • idContext示例的唯一ID,用于输出错误信息时候作为唯一标记。
  • targetRuntime:用于执行代码生成模式。
  • defaultModelType:控制Domain类的生成行为。执行引擎为MyBatis3DynamicSql或者MyBatis3Kotlin时忽略此配置,可选值:
    • conditional:默认值,类似hierarchical,但是只有一个主键的时候会合并所有属性生成在同一个类。
    • flat:所有内容全部生成在一个对象中。
    • hierarchical:键生成一个XXKey对象,Blob等单独生成一个对象,其他简单属性在一个对象中。

targetRuntime属性的可选值比较多,这里做个简单的小结:

属性 功能描述
MyBatis3DynamicSql 默认值,兼容JDK8+MyBatis 3.4.2+,不会生成XML映射文件,忽略<sqlMapGenerator>的配置项,也就是Mapper全部注解化,依赖于MyBatis Dynamic SQL类库
MyBatis3Kotlin 行为类似于MyBatis3DynamicSql,不过兼容Kotlin的代码生成
MyBatis3 提供基本的基于动态SQLCRUD方法和XXXByExample方法,会生成XML映射文件
MyBatis3Simple 提供基本的基于动态SQLCRUD方法,会生成XML映射文件
MyBatis3DynamicSqlV1 已经过时,不推荐使用

所以一般选用MyBatis3或者MyBatis3Simple

<context id="default" targetRuntime="MyBatis3">

<context>标签支持0或N个<property>标签,<property>的可选属性有:

property属性 功能描述 默认值 备注
autoDelimitKeywords 是否使用分隔符号括住数据库关键字 false 例如MySQL中会使用反引号括住关键字
beginningDelimiter 分隔符号的开始符号 "
endingDelimiter 分隔符号的结束号 "
javaFileEncoding 文件的编码 系统默认值 来源于java.nio.charset.Charset
javaFormatter 类名和文件格式化器 DefaultJavaFormatter JavaFormatterDefaultJavaFormatter
targetJava8 是否JDK8和启动其特性 true
kotlinFileEncoding Kotlin文件编码 系统默认值 来源于java.nio.charset.Charset
kotlinFormatter Kotlin类名和文件格式化器 DefaultKotlinFormatter KotlinFormatterDefaultKotlinFormatter
xmlFormatter XML文件格式化器 DefaultXmlFormatter XmlFormatterDefaultXmlFormatter

jdbcConnection标签

<jdbcConnection>标签用于指定数据源的连接信息,它在mybatis-generator-core中对应的实现类为org.mybatis.generator.config.JDBCConnectionConfiguration,主要属性包括:

属性 功能描述 是否必须
driverClass 数据源驱动的全类名 Y
connectionURL JDBC的连接URL Y
userId 连接到数据源的用户名 N
password 连接到数据源的密码 N

commentGenerator标签

<commentGenerator>标签是可选的,用于控制生成的实体的注释内容。它在mybatis-generator-core中对应的实现类为org.mybatis.generator.internal.DefaultCommentGenerator,可以通过可选的type属性指定一个自定义的CommentGenerator实现。<commentGenerator>标签支持0或N个<property>标签,<property>的可选属性有:

property属性 功能描述 默认值
suppressAllComments 是否生成注释 false
suppressDate 是否在注释中添加生成的时间戳 false
dateFormat 配合suppressDate使用,指定输出时间戳的格式 java.util.Date#toString()
addRemarkComments 是否输出表和列的Comment信息 false

建议保持默认值,也就是什么注释都不输出,生成代码干净的实体。

javaTypeResolver标签

<javaTypeResolver>标签是<context>的子标签,用于解析和计算数据库列类型和Java类型的映射关系,该标签只包含一个type属性,用于指定org.mybatis.generator.api.JavaTypeResolver接口的实现类。<javaTypeResolver>标签支持0或N个<property>标签,<property>的可选属性有:

property属性 功能描述 默认值
forceBigDecimals 是否强制把所有的数字类型强制使用java.math.BigDecimal类型表示 false
useJSR310Types 是否支持JSR310,主要是JSR310的新日期类型 false

如果useJSR310Types属性设置为true,那么生成代码的时候类型映射关系如下(主要针对日期时间类型):

数据库(JDBC)类型 Java类型
DATE java.time.LocalDate
TIME java.time.LocalTime
TIMESTAMP java.time.LocalDateTime
TIME_WITH_TIMEZONE java.time.OffsetTime
TIMESTAMP_WITH_TIMEZONE java.time.OffsetDateTime

引入mybatis-generator-core后,可以查看JavaTypeResolver的默认实现为JavaTypeResolverDefaultImpl,从它的源码可以得知一些映射关系:

BIGINT --> Long
BIT --> Boolean
INTEGER --> Integer
SMALLINT --> Short
TINYINT --> Byte
......

有些时候,我们希望INTEGERSMALLINTTINYINT都映射为Integer,那么我们需要覆盖JavaTypeResolverDefaultImpl的构造方法:

public class DefaultJavaTypeResolver extends JavaTypeResolverDefaultImpl {

    public DefaultJavaTypeResolver() {
        super();
        typeMap.put(Types.SMALLINT, new JdbcTypeInformation("SMALLINT",
                new FullyQualifiedJavaType(Integer.class.getName())));
        typeMap.put(Types.TINYINT, new JdbcTypeInformation("TINYINT",
                new FullyQualifiedJavaType(Integer.class.getName())));
    }
}

注意一点的是这种自定义实现JavaTypeResolver接口的方式使用编程式运行MBG会相对方便,如果需要使用Maven插件运行,那么需要把上面的DefaultJavaTypeResolver类打包到插件中。

javaModelGenerator标签

<javaModelGenerator标签>标签是<context>的子标签,主要用于控制实体(Model)类的代码生成行为。它支持的属性如下:

属性 功能描述 是否必须 备注
targetPackage 生成的实体类的包名 Y 例如club.throwable.model
targetProject 生成的实体类文件相对于项目(根目录)的位置 Y 例如src/main/java

<javaModelGenerator标签>标签支持0或N个<property>标签,<property>的可选属性有:

property属性 功能描述 默认值 备注
constructorBased 是否生成一个带有所有字段属性的构造函数 false MyBatis3Kotlin模式下忽略此属性配置
enableSubPackages 是否允许通过Schema生成子包 false 如果为true,例如包名为club.throwable,如果Schemaxyz,那么实体类文件最终会生成在club.throwable.xyz目录
exampleTargetPackage 生成的伴随实体类的Example类的包名 - -
exampleTargetProject 生成的伴随实体类的Example类文件相对于项目(根目录)的位置 - -
immutable 是否不可变 false 如果为true,则不会生成Setter方法,所有字段都使用final修饰,提供一个带有所有字段属性的构造函数
rootClass 为生成的实体类添加父类 - 通过value指定父类的全类名即可
trimStrings Setter方法是否对字符串类型进行一次trim操作 false -

javaClientGenerator标签

<javaClientGenerator>标签是<context>的子标签,主要用于控制Mapper接口的代码生成行为。它支持的属性如下:

属性 功能描述 是否必须 备注
type Mapper接口生成策略 Y <context>标签的targetRuntime属性为MyBatis3DynamicSql或者MyBatis3Kotlin时此属性配置忽略
targetPackage 生成的Mapper接口的包名 Y 例如club.throwable.mapper
targetProject 生成的Mapper接口文件相对于项目(根目录)的位置 Y 例如src/main/java

type属性的可选值如下:

  • ANNOTATEDMAPPERMapper接口生成的时候依赖于注解和SqlProviders(也就是纯注解实现),不会生成XML映射文件。
  • XMLMAPPERMapper接口生成接口方法,对应的实现代码生成在XML映射文件中(也就是纯映射文件实现)。
  • MIXEDMAPPERMapper接口生成的时候复杂的方法实现生成在XML映射文件中,而简单的实现通过注解和SqlProviders实现(也就是注解和映射文件混合实现)。

注意两点:

  • <context>标签的targetRuntime属性指定为MyBatis3Simple的时候,type只能选用ANNOTATEDMAPPER或者XMLMAPPER
  • <context>标签的targetRuntime属性指定为MyBatis3的时候,type可以选用ANNOTATEDMAPPERXMLMAPPER或者MIXEDMAPPER

<javaClientGenerator>标签支持0或N个<property>标签,<property>的可选属性有:

property属性 功能描述 默认值 备注
enableSubPackages 是否允许通过Schema生成子包 false 如果为true,例如包名为club.throwable,如果Schemaxyz,那么Mapper接口文件最终会生成在club.throwable.xyz目录
useLegacyBuilder 是否通过SQL Builder生成动态SQL false
rootInterface 为生成的Mapper接口添加父接口 - 通过value指定父接口的全类名即可

sqlMapGenerator标签

<sqlMapGenerator>标签是<context>的子标签,主要用于控制XML映射文件的代码生成行为。它支持的属性如下:

属性 功能描述 是否必须 备注
targetPackage 生成的XML映射文件的包名 Y 例如mappings
targetProject 生成的XML映射文件相对于项目(根目录)的位置 Y 例如src/main/resources

<sqlMapGenerator>标签支持0或N个<property>标签,<property>的可选属性有:

property属性 功能描述 默认值 备注
enableSubPackages 是否允许通过Schema生成子包 false -

plugin标签

<plugin>标签是<context>的子标签,用于引入一些插件对代码生成的一些特性进行扩展,该标签只包含一个type属性,用于指定org.mybatis.generator.api.Plugin接口的实现类。内置的插件实现见Supplied Plugins。例如:引入org.mybatis.generator.plugins.SerializablePlugin插件会让生成的实体类自动实现java.io.Serializable接口并且添加serialVersionUID属性。

table标签

属性 功能描述 是否必须 备注
tableName 数据库表名称 Y 例如t_order
schema 数据库Schema N -
catalog 数据库Catalog N -
alias 表名称标签 N 如果指定了此值,则查询列的时候结果格式为alias_column
domainObjectName 表对应的实体类名称,可以通过.指定包路径 N 如果指定了bar.User,则包名为bar,实体类名称为User
mapperName 表对应的Mapper接口类名称,可以通过.指定包路径 N 如果指定了bar.UserMapper,则包名为barMapper接口类名称为UserMapper
sqlProviderName 动态SQL提供类SqlProvider的类名称 N -
enableInsert 是否允许生成insert方法 N 默认值为true,执行引擎为MyBatis3DynamicSql或者MyBatis3Kotlin时忽略此配置
enableSelectByPrimaryKey 是否允许生成selectByPrimaryKey方法 N 默认值为true,执行引擎为MyBatis3DynamicSql或者MyBatis3Kotlin时忽略此配置
enableSelectByExample 是否允许生成selectByExample方法 N 默认值为true,执行引擎为MyBatis3DynamicSql或者MyBatis3Kotlin时忽略此配置
enableUpdateByPrimaryKey 是否允许生成updateByPrimaryKey方法 N 默认值为true,执行引擎为MyBatis3DynamicSql或者MyBatis3Kotlin时忽略此配置
enableDeleteByPrimaryKey 是否允许生成deleteByPrimaryKey方法 N 默认值为true,执行引擎为MyBatis3DynamicSql或者MyBatis3Kotlin时忽略此配置
enableDeleteByExample 是否允许生成deleteByExample方法 N 默认值为true,执行引擎为MyBatis3DynamicSql或者MyBatis3Kotlin时忽略此配置
enableCountByExample 是否允许生成countByExample方法 N 默认值为true,执行引擎为MyBatis3DynamicSql或者MyBatis3Kotlin时忽略此配置
enableUpdateByExample 是否允许生成updateByExample方法 N 默认值为true,执行引擎为MyBatis3DynamicSql或者MyBatis3Kotlin时忽略此配置
selectByPrimaryKeyQueryId value指定对应的主键列提供列表查询功能 N 执行引擎为MyBatis3DynamicSql或者MyBatis3Kotlin时忽略此配置
selectByExampleQueryId value指定对应的查询ID提供列表查询功能 N 执行引擎为MyBatis3DynamicSql或者MyBatis3Kotlin时忽略此配置
modelType 覆盖<context>defaultModelType属性 N <context>defaultModelType属性
escapeWildcards 是否对通配符进行转义 N -
delimitIdentifiers 标记匹配表名称的时候是否需要使用分隔符去标记生成的SQL N -
delimitAllColumns 是否所有的列都添加分隔符 N 默认值为false,如果设置为true,所有列名会添加起始和结束分隔符

<table>标签支持0或N个<property>标签,<property>的可选属性有:

property属性 功能描述 默认值 备注
constructorBased 是否为实体类生成一个带有所有字段的构造函数 false 执行引擎为MyBatis3Kotlin的时候此属性忽略
ignoreQualifiersAtRuntime 是否在运行时忽略别名 false 如果为true,则不会在生成表的时候把schemacatalog作为表的前缀
immutable 实体类是否不可变 false 执行引擎为MyBatis3Kotlin的时候此属性忽略
modelOnly 是否仅仅生成实体类 false -
rootClass 如果配置此属性,则实体类会继承此指定的超类 - 如果有主键属性会把主键属性在超类生成
rootInterface 如果配置此属性,则实体类会实现此指定的接口 - 执行引擎为MyBatis3Kotlin或者MyBatis3DynamicSql的时候此属性忽略
runtimeCatalog 指定运行时的Catalog - 当生成表和运行时的表的Catalog不一样的时候可以使用该属性进行配置
runtimeSchema 指定运行时的Schema - 当生成表和运行时的表的Schema不一样的时候可以使用该属性进行配置
runtimeTableName 指定运行时的表名称 - 当生成表和运行时的表的表名称不一样的时候可以使用该属性进行配置
selectAllOrderByClause 指定字句内容添加到selectAll()方法的order by子句之中 - 执行引擎为MyBatis3Simple的时候此属性才适用
trimStrings 实体类的字符串类型属性会做trim处理 - 执行引擎为MyBatis3Kotlin的时候此属性忽略
useActualColumnNames 是否使用列名作为实体类的属性名 false -
useColumnIndexes XML映射文件中生成的ResultMap使用列索引定义而不是列名称 false 执行引擎为MyBatis3Kotlin或者MyBatis3DynamicSql的时候此属性忽略
useCompoundPropertyNames 是否把列名和列备注拼接起来生成实体类属性名 false -

<table>标签还支持众多的非property的子标签:

  • 0或1个<generatedKey>用于指定主键生成的规则,指定此标签后会生成一个<selectKey>标签

    <!-- column:指定主键列 -->
    <!-- sqlStatement:查询主键的SQL语句,例如填写了MySql,则使用SELECT LAST_INSERT_ID() -->
    <!-- type:可选值为pre或者post,pre指定selectKey标签的order为BEFORE,post指定selectKey标签的order为AFTER -->
    <!-- identity:true的时候,指定selectKey标签的order为AFTER -->
    <generatedKey column="id" sqlStatement="MySql" type="post" identity="true" />
    
  • 0或1个<domainObjectRenamingRule>用于指定实体类重命名规则

    <!-- searchString中正则命中的实体类名部分会替换为replaceString -->
    <domainObjectRenamingRule searchString="^Sys" replaceString=""/>
    <!-- 例如 SysUser会变成User -->
    <!-- 例如 SysUserMapper会变成UserMapper -->
    
  • 0或1个<columnRenamingRule>用于指定列重命名规则

    <!-- searchString中正则命中的列名部分会替换为replaceString -->
    <columnRenamingRule searchString="^CUST_" replaceString=""/>
    <!-- 例如 CUST_BUSINESS_NAME会变成BUSINESS_NAME(useActualColumnNames=true) -->
    <!-- 例如 CUST_BUSINESS_NAME会变成businessName(useActualColumnNames=false) -->
    
  • 0或N个<columnOverride>用于指定具体列的覆盖映射规则

    <!-- column:指定要覆盖配置的列 -->
    <!-- property:指定要覆盖配置的属性 -->
    <!-- delimitedColumnName:是否为列名添加定界符,例如`{column}` -->
    <!-- isGeneratedAlways:是否一定生成此列 -->
    <columnOverride column="customer_name" property="customerName" javaType="" jdbcType="" typeHandler="" delimitedColumnName="" isGeneratedAlways="">
       <!-- 覆盖table或者javaModelGenerator级别的trimStrings属性配置 -->
       <property name="trimStrings" value="true"/>
    <columnOverride/>
    
  • 0或N个<ignoreColumn>用于指定忽略生成的列

    <ignoreColumn column="version" delimitedColumnName="false"/>
    

实战注意点

使用纯注解

如果使用纯注解,则需要引入mybatis-dynamic-sql

<dependency>
    <groupId>org.mybatis.dynamic-sql</groupId>
    <artifactId>mybatis-dynamic-sql</artifactId>
    <version>1.1.4</version>
</dependency>

并且需要修改两个位置:

<context id="default" targetRuntime="MyBatis3DynamicSql">
...

<javaClientGenerator type="ANNOTATEDMAPPER"
...

使用极简XML映射文件

极简XML映射文件生成只需要简单修改配置文件:

<context id="default" targetRuntime="MyBatis3Simple">
...

<javaClientGenerator type="XMLMAPPER"
...

编程式自定义类型映射

所有的非长整型的数字,可以统一使用Integer接收,因此需要自定义类型映射。编写映射器如下:

public class DefaultJavaTypeResolver extends JavaTypeResolverDefaultImpl {

    public DefaultJavaTypeResolver() {
        super();
        typeMap.put(Types.SMALLINT, new JdbcTypeInformation("SMALLINT",
                new FullyQualifiedJavaType(Integer.class.getName())));
        typeMap.put(Types.TINYINT, new JdbcTypeInformation("TINYINT",
                new FullyQualifiedJavaType(Integer.class.getName())));
    }
}

此时最好使用编程式运行代码生成器,修改XML配置文件:

<javaTypeResolver type="club.throwable.mbg.DefaultJavaTypeResolver">
        <property name="forceBigDecimals" value="false"/>
</javaTypeResolver>
...

运行方法代码如下:

public class Main {

    public static void main(String[] args) throws Exception {
        List<String> warnings = new ArrayList<>();
        // 如果已经存在生成过的文件是否进行覆盖
        boolean overwrite = true;
        ConfigurationParser cp = new ConfigurationParser(warnings);
        Configuration config = cp.parseConfiguration(Main.class.getResourceAsStream("/generator-configuration.xml"));
        DefaultShellCallback callback = new DefaultShellCallback(overwrite);
        MyBatisGenerator generator = new MyBatisGenerator(config, callback, warnings);
        generator.generate(null);
    }
}

数据库的order_statusTINYINT类型,生成出来的文件中的orderStatus字段全部替换使用Integer类型定义。

★通过编码和配置文件运行

通过编码方式去运行插件先需要引入mybatis-generator-core依赖:

<dependency>
    <groupId>org.mybatis.generator</groupId>
    <artifactId>mybatis-generator-core</artifactId>
    <version>1.4.0</version>
</dependency>

假设编写好的XML配置文件是ClassPath下的generator-configuration.xml,那么使用代码生成器的编码方式大致如下:

List<String> warnings = new ArrayList<>();
// 如果已经存在生成过的文件是否进行覆盖
boolean overwrite = true;
File configFile = new File("ClassPath路径/generator-configuration.xml");
ConfigurationParser cp = new ConfigurationParser(warnings);
Configuration config = cp.parseConfiguration(configFile);
DefaultShellCallback callback = new DefaultShellCallback(overwrite);
MyBatisGenerator generator = new MyBatisGenerator(config, callback, warnings);
generator.generate(null);

通过Maven插件运行

如果使用Maven插件,那么不需要引入mybatis-generator-core依赖,只需要引入一个Maven的插件mybatis-generator-maven-plugin

<plugins>
    <plugin>
        <groupId>org.mybatis.generator</groupId>
        <artifactId>mybatis-generator-maven-plugin</artifactId>
        <version>1.4.0</version>
        <executions>
            <execution>
                <id>Generate MyBatis Artifacts</id>
                <goals>
                    <goal>generate</goal>
                </goals>
            </execution>
        </executions>
        <configuration>
            <!-- 输出详细信息 -->
            <verbose>true</verbose>
            <!-- 覆盖生成文件 -->
            <overwrite>true</overwrite>
            <!-- 定义配置文件 -->
            <configurationFile>${basedir}/src/main/resources/generator-configuration.xml</configurationFile>
        </configuration>
    </plugin>
</plugins>

mybatis-generator-maven-plugin的更详细配置和可选参数可以参考:Running With Maven。插件配置完毕之后,使用下面的命令即可运行:

mvn mybatis-generator:generate

idea使用技巧

引入本地DTD文件

在没有联网的情况下,让dtd约束继续起作用,并且出现标签提示,可以通过引入本地dtd文件来实现。

1、下载dtd:在浏览器中输入dtd的网络地址即可实现下载。比如:http://mybatis.org/dtd/mybatis-3-config.dtd

2、将下载的dtd拷贝到本地的一个目录下

3、idea操作路径:File — Settings — Languages & Frameworks。其中URI复制dtd的网络地址,File选择dtd文件在本地的地址,就行了。

注意:在mybatis的核心jar包中就提供了mybatis-3-config.dtd

创建文件模板

idea提供了大量的内置文件模板template,可以自定义模板,避免重复,提高效率。

创建入口1:右键—new—Edit file Templates

创建入口2:File—settings—editor—File and Code Templates

使用入口:右键—new—选择模板名称

自动生成mapper.xml

安装mybatisX插件(idea:点开File,点开settings,点开Pluign下载插件)

这样的话我们一旦写好接口方法直接alt+enter就可以自动在mapper.xml中生成相应的标签