Mybatis
一、Mybatis介绍
mybatis是一个优秀的基于java的持久层框架,它内部封装了jdbc,使开发者只需要关注sql语句本身,而不需要花费精力去处理加载驱动、创建连接、创建statement等繁杂的过程。
mybatis通过xml或注解的方式将要执行的各种statement配置起来,并通过java对象和statement中sql的动态参数进行映射生成最终执行的sql语句,最后由mybatis框架执行sql并将结果映射为java对象并返回。
采用ORM思想解决了实体和数据库映射的问题,对jdbc进行了封装,屏蔽了jdbc api底层访问细节,使我们不用与jdbc api打交道,就可以完成对数据库的持久化操作。
原生jdbc访问数据库
package com.han.mapper; import com.han.pojo.Book; import java.sql.Connection; import java.sql.DriverManager; import java.sql.PreparedStatement; import java.sql.ResultSet; @SuppressWarnings("all") public class BookMapper { public static void main(String[] args) throws Exception { // 注册驱动 Class.forName("com.mysql.cj.jdbc.Driver"); // 获取连接 Connection connection = DriverManager.getConnection("jdbc:mysql:///mybatis?serverTimezone=UTC", "root", "root"); // 获取预处理对象 String sql = "select * from book"; PreparedStatement preparedStatement = connection.prepareStatement(sql); // 执行sql语句 ResultSet resultSet = preparedStatement.executeQuery(); // 结果处理 Book book = null; while (resultSet.next()){ int id = resultSet.getInt("id"); String name = resultSet.getString("name"); book = new Book(); book.setId(id); book.setName(name); System.out.println(book); } // 资源关闭 resultSet.close(); preparedStatement.close(); connection.close(); } }
输出结果
Book{id=1, name='Miss XMS'} Book{id=2, name='Miss XMS'} Book{id=3, name='Miss XMS'} Book{id=4, name='Miss XMS'} Book{id=5, name='Miss XMS'} Book{id=6, name='Miss XMS'} Book{id=7, name='Miss XMS'} Book{id=8, name='Miss XMS'} Book{id=9, name='Miss XMS'}
可以看出,在与数据库的交互,其中很多操作都是重复的,代码冗余,而且sql是写死的,难道我们要每写一个与数据库交互的逻辑,都要这么写吗?
1、数据库链接创建、释放频繁造成系统资源浪费从而影响系统性能,如果使用数据库链接池可解决此问题。
2、Sql语句在代码中硬编码,造成代码不易维护,实际应用sql变化的可能较大,sql变动需要改变java代码。
3、使用preparedStatement向占有位符号传参数存在硬编码,因为sql语句的where条件不一定,可能多也可能少,修改sql还要修改代码,系统不易维护。
4、对结果集解析存在硬编码(查询列名),sql变化导致解析代码变化,系统不易维护,如果能将数据库记录封装成pojo对象解析比较方便。
二、Mybatis快速入门
基于xml
<dependencies> <!-- https://mvnrepository.com/artifact/org.mybatis/mybatis --> <dependency> <groupId>org.mybatis</groupId> <artifactId>mybatis</artifactId> <version>3.5.5</version> </dependency> <!-- https://mvnrepository.com/artifact/mysql/mysql-connector-java --> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>8.0.20</version> </dependency> </dependencies>
<?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> <!-- 数据源环境配置 --> <environments default="mysql"> <environment id="mysql"> <transactionManager type="jdbc"/> <dataSource type="POOLED"> <!-- MySQL版本8.0+ --> <property name="driver" value="com.mysql.cj.jdbc.Driver"/> <property name="url" value="jdbc:mysql:///mybatis?serverTimezone=UTC"/> <property name="username" value="root"/> <property name="password" value="root"/> </dataSource> </environment> </environments> <!-- 映射文件路径 --> <mappers> <package name="com.han.mapper"/> </mappers> </configuration>
public interface BookMapper { /** * 查询所有 * @return List<Book> */ List<Book> findAll(); }
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> <mapper namespace="com.han.mapper.BookMapper"> <select id="findAll" resultType="com.han.pojo.Book"> select * from book </select> </mapper>
public class BeanTest { @Test public void m1() throws Exception { // 读取配置文件 InputStream inputStream = Resources.getResourceAsStream("mybatis.xml"); // 创建SqlSessionFactoryBuilder SqlSessionFactoryBuilder sqlSessionFactoryBuilder = new SqlSessionFactoryBuilder(); // 使用SqlSessionFactoryBuilder创建SqlSessionFactory SqlSessionFactory sessionFactory = sqlSessionFactoryBuilder.build(inputStream); // 获取处理对象SqlSession SqlSession sqlSession = sessionFactory.openSession(); // 获取代理对象 BookMapper bookMapper = sqlSession.getMapper(BookMapper.class); // 结果处理 List<Book> books = bookMapper.findAll(); for (Book book : books) { System.out.println(book); } // 资源关闭 sqlSession.close(); inputStream.close(); } }
输出结果
Book{id=1, name='Miss XMS'} Book{id=2, name='Miss XMS'} Book{id=3, name='Miss XMS'} Book{id=4, name='Miss XMS'} Book{id=5, name='Miss XMS'} Book{id=6, name='Miss XMS'} Book{id=7, name='Miss XMS'} Book{id=8, name='Miss XMS'} Book{id=9, name='Miss XMS'}
基于注解
/** * 查询 * @param id id * @return book */ @Select("select * from book where id = #{id}") Book getOneById(Integer id);
@Test public void m2(){ Book book = bookMapper.getOneById(1); System.out.println(book); }
输出结果
Book{id=1, name='Miss XMS'}
在配置文件mybatis中指定映射文件路径,多种写法
<!-- 映射文件路径 --> <mappers> <mapper class="com.han.mapper.BookMapper"/> </mappers> <mappers> <package name="com.han.mapper"/> </mappers> <mappers> <mapper resource="com/han/mapper/BookMapper.xml"/> </mappers>
新增操作
/** * 新增 * @param book */ void insert(Book book);
<insert id="insert" parameterType="com.han.pojo.Book">
insert into book (name) value (#{name})
</insert>
@Test public void m3(){ Book book = new Book(); book.setName("Life"); bookMapper.insert(book); System.out.println(book); }
输出结果
Book{id=0, name='Life'}
查看数据库添加成功,如果我们要获取添加后的id,有两种方法。
一种是基于XML_1
<insert id="insert" parameterType="com.han.pojo.Book"> <selectKey resultType="int" keyProperty="id" keyColumn="id"> select last_insert_id(); </selectKey> insert into book (name) value (#{name}) </insert>
一种是基于注解_1
@Insert("insert into book(name) value (#{name})") @SelectKey(keyColumn = "id", keyProperty = "id", resultType = int.class, before = false, statement="select @@identity") void insert2(Book book);
一种是基于XML_2
<insert id="insert" parameterType="com.han.pojo.Book" useGeneratedKeys="true" keyProperty="id"> insert into book (name) value (#{name}) </insert>
输出结果
Book{id=19, name='Life'}
insert into student <trim prefix="(" suffix=")" suffixOverrides=","> <if test="xxx != null"> xxx </if> <if test="xxx != null"> xxx </if> </trim>
作用:绑定,上面效果 insert into student (xxx,xxx)
模糊查询
第一种使用${}
<select id="findBookByName" resultType="com.han.pojo.Book" parameterType="String"> select * from book where name like '%${value}%' </select>
<select id="findBookByName" resultType="com.han.pojo.Book" parameterType="String"> select * from book where name like "%" #{value} "%" </select>
<select id="findBookByName" resultType="com.han.pojo.Book" parameterType="String"> <bind name="value" value="'%'+_value+'%'"/> select * from book where name like #{value} </select>
第四种基于注解 concat
@Select("select * from book where name like concat('%',#{name},'%')")
List<Book> findBookByName2(String name);
#{}和${}区别
#{}表示一个占位符号。通过#{}可以实现preparedStatement向占位符中设置值,自动进行java类型和jdbc类型转换,#{}可以有效防止sql注入。 #{}可以接收简单类型值或pojo属性值。 如果parameterType传输单个简单类型值,#{}括号中可以是value或其它名称。
${}表示拼接sql串。通过${}可以将parameterType 传入的内容拼接在sql中且不进行jdbc类型转换, ${}可以接收简单类型值或pojo属性值,如果parameterType传输单个简单类型值,${}括号中只能是value
三、Mybatis的参数
parameterType
基本类型和String我们可以直接写类型名称,也可以使用包名.类名的方式,例如:java.lang.String。 实体类型,目前我们只能使用全限定类名。 究其原因,是mybaits在加载时已经把常用的数据类型注册了别名,从而我们在使用时可以不写包名。如果想实体类不写全路径类名就写别名(不建议)。typeAliases(配置文件中)
<typeAliases> <!-- 单个别名 --> <typeAlias type="com.han.pojo.Book" alias="book"/> </typeAliases> <typeAliases> <!-- 指定包路径下,别名为类名 --> <package name="com.han.pojo"/> </typeAliases>
resultType
resultType属性可以指定结果集的类型,它支持基本类型和实体类类型。
实体类中的属性名称必须和查询语句中的列名保持一致,否则无法实现封装。
resultMap
resultMap标签可以建立查询的列名和实体类的属性名称不一致时建立对应关系。从而实现封装。 在select标签中使用resultMap属性指定引用即可。同时resultMap可以实现将查询结果映射为复杂类型的pojo,比如在查询结果映射对象中包括pojo和list实现一对一查询和一对多查询。
<resultMap id="BookMap" type="com.han.pojo.Book"> <id property="id" column="id" javaType="int"/> <result property="name" column="name" javaType="string"/> </resultMap>
<resultMap id= type=> id 给结果类型起一个唯一标识 type 要封装的对象类型 <resultMap>包含 <id/> 主键 <result/> 非主键字段 <association/> 要封装的对象中包含的对象(一对一) <collection/> 要封装的对象中包含的集合对象(一对多) <resultMap>包含子标签内 id标签:用于指定主键字段 result标签:用于指定非主键字段 column属性:用于指定数据库列名 property属性:用于指定实体类属性名称
<sql id="defaultSql"> select * from person </sql> <select id="findAll" resultType="com.han.pojo.Person"> <include refid="defaultSql"></include> </select>
四、SqlMapConfig.xml
配置顺序
-properties(属性)
--property
-settings(全局配置参数)
--setting
-typeAliases(类型别名)
--typeAliase
--package
-typeHandlers(类型处理器)
-objectFactory(对象工厂)
-plugins(插件)
-environments(环境集合属性对象)
--environment(环境子属性对象)
---transactionManager(事务管理)
---dataSource(数据源)
-mappers(映射器)
--mapper
--package
<select id="findByNameAndAddr" resultType="com.han.pojo.Person" > select * from person where 1 = 1 <if test="name != null and name.trim() != ''"> and name = #{name} </if> <if test="addr != null and addr.trim() != ''"> and addr = #{addr} </if> </select>
<where>
<select id="findByNameAndAddr" resultType="com.han.pojo.Person" > select * from person <where> <if test="name != null and name.trim() != ''"> and name = #{name} </if> <if test="addr != null and addr.trim() != ''"> and addr = #{addr} </if> </where> </select>
<foreach>
<foreach>标签用于遍历集合
collection:代表要遍历的集合元素,注意编写时不要写#{}
open:代表语句的开始部分
close:代表结束部分
item:代表遍历集合的每个元素,生成的变量名
sperator:代表分隔符
<select id="findByIds" resultType="com.han.pojo.Person"> select * from person <where> <if test="ids != null"> id in <foreach collection="ids" item="id" separator="," close=")" open="("> #{id} </foreach> </if> </where> </select>
package com.han.pojo; public class Count { private Long id; private Float money; public Long getId() { return id; } public void setId(Long id) { this.id = id; } public Float getMoney() { return money; } public void setMoney(Float money) { this.money = money; } @Override public String toString() { return "Count{" + "id=" + id + ", money=" + money + '}'; } }
在Count中新增属性
private Person person;
<resultMap id="countPersonMap" type="com.han.pojo.Count"> <id property="id" column="id" javaType="Long"/> <result property="money" column="money" javaType="float"/> <association property="person" javaType="com.han.pojo.Person"> <id property="id" column="id" javaType="Long"/> <result property="name" column="name" javaType="String"/> <result property="idCard" column="idCard" javaType="String"/> <result property="phone" column="phone" javaType="String"/> <result property="addr" column="addr" javaType="String"/> </association> </resultMap> <select id="findCountAndPerson" resultMap="countPersonMap"> select * from person p join count c on c.id = p.id where c.id = #{value} </select>
测试
/** * 一对一查询 */ @Test public void testFindPersonAndCount(){ CountMapper countMapper = sqlSession.getMapper(CountMapper.class); System.out.println(countMapper.findCountAndPerson(1L)); }
输出结果
Count{id=1, money=100.0, person=Person{id=1, name='XMS', idCard='111111111111111111', phone='15823994255', addr='福州'}}
package com.han.pojo; public class UR { private Long rid; private Long uid; public Long getRid() { return rid; } public void setRid(Long rid) { this.rid = rid; } public Long getUid() { return uid; } public void setUid(Long uid) { this.uid = uid; } @Override public String toString() { return "UR{" + "rid=" + rid + ", uid=" + uid + '}'; } }
public interface URMapper { List<UR> findRolesByUid(Long uid); }
<?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> <mapper namespace="com.han.mapper.URMapper"> <select id="findRolesByUid" resultType="com.han.pojo.UR"> select * from ur where uid = #{value} </select> </mapper>
测试
@Test public void testFindRolesByUid(){ URMapper urMapper = sqlSession.getMapper(URMapper.class); List<UR> urs = urMapper.findRolesByUid(1L); for (UR ur : urs) { System.out.println(ur); } }
输出结果
UR{rid=1, uid=1}
UR{rid=2, uid=1}
多对多,需要中间表
表1 user
1 XMS
2 HWH
表2 role
1 朋友 旧人 2 同学 故人 3 路人 不相逢
表3 中间表ur
1 1 1 2 2 3
<resultMap id="userRoleMap" type="com.han.pojo.User"> <id property="id" column="id" javaType="Long"/> <result property="name" column="name" javaType="String"/> <collection property="roles" ofType="com.han.pojo.Role"> <id property="roleId" column="roleId" javaType="Long"/> <result property="roleDesc" column="roleDesc" javaType="String"/> <result property="roleName" column="roleName" javaType="String"/> </collection> </resultMap> <select id="findUserAndRoleById" resultMap="userRoleMap"> select * from role r inner join ur l on r.roleid = l.rid inner join user u on u.id = l.uid where u.id = #{value} </select>
测试
@Test public void testFindUserAndRoleById(){ UserMapper userMapper = sqlSession.getMapper(UserMapper.class); User user = userMapper.findUserAndRoleById(1L); System.out.println(user); }
输出结果
User{id=1, name='XMS', roles=[Role{roleId=1, roleName='朋友', roleDesc='旧人'}, Role{roleId=2, roleName='同学', roleDesc='故人'}]}
九、延迟加载
<!-- 开启延迟加载 --> <settings> <setting name="lazyLoadingEnabled" value="true"/> <setting name="aggressiveLazyLoading" value="false"/> </settings>
假定一个Role对应一个User
在Role中新增
private User user; public User getUser() { return user; } public void setUser(User user) { this.user = user; }
在RoleMapper中
/** * 查询Role对应的User * @param rid * @return */ Role findRoleAndUser(Long rid);
在RoleMapper.xml
<resultMap id="roleUserMap" type="com.han.pojo.Role"> <id property="roleId" column="roleId" javaType="Long"/> <result property="roleName" column="roleName" javaType="String"/> <result property="roleDesc" column="roleDesc" javaType="String"/> <association property="user" javaType="com.han.pojo.User" column="roleId" select="com.han.mapper.UserMapper.findByRids"/> </resultMap> <select id="findRoleAndUser" resultMap="roleUserMap"> select * from role where roleid = #{value} </select>
在UserMapper.xml
<select id="findByRids" resultType="com.han.pojo.User"> select * from user u join (select uid from ur where rid = #{value}) l on u.id = l.uid </select>
测试
@Test public void testFindRoleAndUser(){ RoleMapper roleMapper = sqlSession.getMapper(RoleMapper.class); Role role = roleMapper.findRoleAndUser(1L); System.out.println(role); }
输出结果
当需要输出(System.out.println(role);)
per.RoleMapper.findRoleAndUser - ==> Preparing: select * from role where roleid = ? per.RoleMapper.findRoleAndUser - ==> Parameters: 1(Long) per.RoleMapper.findRoleAndUser - <== Total: 1 n.mapper.UserMapper.findByRids - ==> Preparing: select * from user u join (select uid from ur where rid = ?) l on u.id = l.uid n.mapper.UserMapper.findByRids - ==> Parameters: 1(Integer) n.mapper.UserMapper.findByRids - <== Total: 1 Role{roleId=1, roleName='朋友', roleDesc='旧人', user=User{id=1, name='XMS', roles=null}}
当不需要输出(注释System.out.println(role);)
per.RoleMapper.findRoleAndUser - ==> Preparing: select * from role where roleid = ? per.RoleMapper.findRoleAndUser - ==> Parameters: 1(Long) per.RoleMapper.findRoleAndUser - <== Total: 1
10.1 一级缓存
一级缓存是默认开启的,并且无法关闭,不过可以清除(clearCache)
@Test public void testFindAll() { UserMapper userMapper = sqlSession.getMapper(UserMapper.class); List<User> userList1 = userMapper.findAll(); List<User> userList2 = userMapper.findAll(); }
输出结果
.han.mapper.UserMapper.findAll - ==> Preparing: select * from user .han.mapper.UserMapper.findAll - ==> Parameters: .han.mapper.UserMapper.findAll - <== Total: 2
可以看到,两次查询findAll实际上只查询了一次
使用clearCache
han.mapper.UserMapper.findAll - ==> Preparing: select * from user han.mapper.UserMapper.findAll - ==> Parameters: han.mapper.UserMapper.findAll - <== Total: 2 han.mapper.UserMapper.findAll - ==> Preparing: select * from user han.mapper.UserMapper.findAll - ==> Parameters: han.mapper.UserMapper.findAll - <== Total: 2
此时查询了两次,说明缓存被清除
分析一级缓存的范围是SqlSession,当在两次相同查询之间发生新增,修改,删除,commit,close都会导致一级缓存失效。
.han.mapper.UserMapper.findAll - ==> Preparing: select * from user .han.mapper.UserMapper.findAll - ==> Parameters: .han.mapper.UserMapper.findAll - <== Total: 3 .han.mapper.UserMapper.insert - ==> Preparing: insert into user(name) value (?) .han.mapper.UserMapper.insert - ==> Parameters: Miss(String) .han.mapper.UserMapper.insert - <== Updates: 1 .han.mapper.UserMapper.findAll - ==> Preparing: select * from user .han.mapper.UserMapper.findAll - ==> Parameters: .han.mapper.UserMapper.findAll - <== Total: 4
DEBUG发现insert最终会执行clearLocalCache(一级缓存又称本地缓存)
@Test public void testFindAll() { User user = new User(); user.setName("Day"); UserMapper userMapper = sqlSession.getMapper(UserMapper.class); List<User> userList1 = userMapper.findAll(); userMapper.insert(user); List<User> userList2 = userMapper.findAll(); }
@Override public void clearLocalCache() { if (!closed) { localCache.clear(); localOutputParameterCache.clear(); } } @Override public void clear() { cache.clear(); }
开启二级缓存
<!-- 开启二级缓存的支持 --> <setting name="cacheEnabled" value="true"/>
在映射文件中
<cache/>
<select id="findAll" resultType="com.han.pojo.UR" useCache="true"> select * from ur </select>
@Test public void testCache() throws Exception { InputStream inputStream = Resources.getResourceAsStream("SqlMapConfig.xml"); SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream); SqlSession sqlSession1 = sqlSessionFactory.openSession(); URMapper urMapper1 = sqlSession1.getMapper(URMapper.class); List<UR> urList1 = urMapper1.findAll(); System.out.println(urList1); sqlSession1.commit(); sqlSession1.close(); SqlSession sqlSession2 = sqlSessionFactory.openSession(); URMapper urMapper2 = sqlSession2.getMapper(URMapper.class); List<UR> urList2 = urMapper2.findAll(); System.out.println(urList2); sqlSession2.close(); }
输出结果
可以看到只查询了一次。
@Insert:实现新增
@Update:实现更新
@Delete:实现删除
@Select:实现查询
@Result:实现结果集封装
@Results:可以与@Result一起使用,封装多个结果集
@ResultMap:实现引用@Results定义的封装
@One:实现一对一结果集封装
@Many:实现一对多结果集封装
@SelectProvider: 实现动态SQL映射
@CacheNamespace:实现注解二级缓存的使用
使用注解查询一对多
@Select("select * from role where roleid = #{value}") @Results(id = "roleUserResult", value = { @Result(id = true, property = "roleId", column = "roleId", javaType = Long.class), @Result(property = "roleName", column = "roleName", javaType = String.class), @Result(property = "roleDesc", column = "roleDesc", javaType = String.class), @Result(property = "user", javaType = User.class, column = "roleId", one = @One(select = "com.han.mapper.UserMapper.findByRids")) } ) Role findRoleAndUser(Long rid);
使用注解查询多对多
@Select("select * from user where id = #{id}") @Results(id = "userRoleResult", value = { @Result(id = true, property = "id", column = "id", javaType = Long.class), @Result(property = "name", column = "name", javaType = String.class), @Result(property = "roles", column = "id", many = @Many(select = "com.han.mapper.RoleMapper.findRoleByUid")) }) User findUserAndRoleById(Long id);
<!-- https://mvnrepository.com/artifact/com.github.pagehelper/pagehelper --> <dependency> <groupId>com.github.pagehelper</groupId> <artifactId>pagehelper</artifactId> <version>5.1.11</version> </dependency>
SqlMapConfig.xml
<plugins> <!-- com.github.pagehelper为PageHelper类所在包名 --> <plugin interceptor="com.github.pagehelper.PageInterceptor"> <!-- 使用下面的方式配置参数,后面会有所有的参数介绍 --> <property name="param1" value="value1"/> </plugin> </plugins>
//第一种,Mapper接口方式的调用,推荐这种使用方式。 PageHelper.startPage(1, 10); List<Country> list = countryMapper.selectIf(1); //第二种,Mapper接口方式的调用,推荐这种使用方式。 PageHelper.offsetPage(1, 10); List<Country> list = countryMapper.selectIf(1);
十三、基于官网
12.1 配置
SqlMapConfig.xml
MyBatis 的配置文件包含了会深深影响 MyBatis 行为的设置和属性信息。 配置文档的顶层结构如下:
configuration(配置)
properties(属性)
settings(设置)
typeAliases(类型别名)
typeHandlers(类型处理器)
objectFactory(对象工厂)
plugins(插件)
environments(环境配置)
environment(环境变量)
transactionManager(事务管理器)
dataSource(数据源)
databaseIdProvider(数据库厂商标识)
mappers(映射器)
properties(属性)
示例
定义db.properties
jdbc.driver=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql:///han
jdbc.username=root
jdbc.password=root
在SqlMapConfig.xml
<properties resource="db.properties"/> <!-- 数据源配置 --> <environments default="development"> <environment id="development"> <transactionManager type="JDBC"/> <dataSource type="POOLED"> <property name="driver" value="${jdbc.driver}"/> <property name="url" value="${jdbc.url}"/> <property name="username" value="${jdbc.username}"/> <property name="password" value="${jdbc.password}"/> </dataSource> </environment> </environments>
settings(设置)
注解 | 描述 | 默认值 |
cacheEnabled | 全局性地开启或关闭所有映射器配置文件中已配置的任何缓存。 | true |
lazyLoadingEnabled | 延迟加载的全局开关。当开启时,所有关联对象都会延迟加载。 特定关联关系中可通过设置 fetchtype 属性来覆盖该项的开关状态。 | false |
aggressiveLazyLoading | 开启时,任一方法的调用都会加载该对象的所有延迟加载属性。 否则,每个延迟加载属性会按需加载(参考 lazyLoadTriggerMethods)。 | false |
mapUnderscoreToCamelCase | 是否开启驼峰命名自动映射,即从经典数据库列名 A_COLUMN 映射到经典 Java 属性名 aColumn。 | False |
<settings> <setting name="cacheEnabled" value="true"/> <setting name="lazyLoadingEnabled" value="true"/> <setting name="multipleResultSetsEnabled" value="true"/> <setting name="useColumnLabel" value="true"/> <setting name="useGeneratedKeys" value="false"/> <setting name="autoMappingBehavior" value="PARTIAL"/> <setting name="autoMappingUnknownColumnBehavior" value="WARNING"/> <setting name="defaultExecutorType" value="SIMPLE"/> <setting name="defaultStatementTimeout" value="25"/> <setting name="defaultFetchSize" value="100"/> <setting name="safeRowBoundsEnabled" value="false"/> <setting name="mapUnderscoreToCamelCase" value="false"/> <setting name="localCacheScope" value="SESSION"/> <setting name="jdbcTypeForNull" value="OTHER"/> <setting name="lazyLoadTriggerMethods" value="equals,clone,hashCode,toString"/> </settings>
typeAliases(类型别名)
类型别名可为 Java 类型设置一个缩写名字。 它仅用于 XML 配置,意在降低冗余的全限定类名书写。
<!-- 单个类型指定 --> <typeAliases> <typeAlias type="com.han.pojo.User" alias="user"/> </typeAliases> <!-- 整个包指定,在没有注解的情况下,会使用 Bean 的首字母小写的非限定类名来作为它的别名 --> <typeAliases> <package name="com.han.pojo"/> </typeAliases>
类型处理器(typeHandlers)
MyBatis 在设置预处理语句(PreparedStatement)中的参数或从结果集中取出一个值时, 都会用类型处理器将获取到的值以合适的方式转换成 Java 类型。下表描述了一些默认的类型处理器。 从 3.4.5 开始,MyBatis 默认支持 JSR-310(日期和时间 API) 。
objectFactory(对象工厂)
每次 MyBatis 创建结果对象的新实例时,它都会使用一个对象工厂(ObjectFactory)实例来完成实例化工作。 默认的对象工厂需要做的仅仅是实例化目标类,要么通过默认无参构造方法,要么通过存在的参数映射来调用带有参数的构造方法。 如果想覆盖对象工厂的默认行为,可以通过创建自己的对象工厂来实现。
// ExampleObjectFactory.java public class ExampleObjectFactory extends DefaultObjectFactory { public Object create(Class type) { return super.create(type); } public Object create(Class type, List<Class> constructorArgTypes, List<Object> constructorArgs) { return super.create(type, constructorArgTypes, constructorArgs); } public void setProperties(Properties properties) { super.setProperties(properties); } public <T> boolean isCollection(Class<T> type) { return Collection.class.isAssignableFrom(type); } }
<!-- mybatis-config.xml --> <objectFactory type="org.mybatis.example.ExampleObjectFactory"> <property name="someProperty" value="100"/> </objectFactory>
插件(plugins)
MyBatis 允许你在映射语句执行过程中的某一点进行拦截调用。默认情况下,MyBatis 允许使用插件来拦截的方法调用包括:
-
Executor (update, query, flushStatements, commit, rollback, getTransaction, close, isClosed)
-
ParameterHandler (getParameterObject, setParameters)
-
ResultSetHandler (handleResultSets, handleOutputParameters)
-
StatementHandler (prepare, parameterize, batch, update, query)
这些类中方法的细节可以通过查看每个方法的签名来发现,或者直接查看 MyBatis 发行包中的源代码。 如果你想做的不仅仅是监控方法的调用,那么你最好相当了解要重写的方法的行为。 因为在试图修改或重写已有方法的行为时,很可能会破坏 MyBatis 的核心模块。 这些都是更底层的类和方法,所以使用插件的时候要特别当心。
通过 MyBatis 提供的强大机制,使用插件是非常简单的,只需实现 Interceptor 接口,并指定想要拦截的方法签名即可。
// ExamplePlugin.java @Intercepts({@Signature( type= Executor.class, method = "update", args = {MappedStatement.class,Object.class})}) public class ExamplePlugin implements Interceptor { private Properties properties = new Properties(); public Object intercept(Invocation invocation) throws Throwable { // implement pre processing if need Object returnObject = invocation.proceed(); // implement post processing if need return returnObject; } public void setProperties(Properties properties) { this.properties = properties; } }
<!-- mybatis-config.xml --> <plugins> <plugin interceptor="org.mybatis.example.ExamplePlugin"> <property name="someProperty" value="100"/> </plugin> </plugins>
环境配置(environments)
<environments default="development"> <environment id="development"> <transactionManager type="JDBC"> <property name="..." value="..."/> </transactionManager> <dataSource type="POOLED"> <property name="driver" value="${driver}"/> <property name="url" value="${url}"/> <property name="username" value="${username}"/> <property name="password" value="${password}"/> </dataSource> </environment> </environments>
注意一些关键点:
-
默认使用的环境 ID(比如:default="development")。
-
每个 environment 元素定义的环境 ID(比如:id="development")。
-
事务管理器的配置(比如:type="JDBC")。
-
数据源的配置(比如:type="POOLED")。
默认环境和环境 ID 顾名思义。 环境可以随意命名,但务必保证默认的环境 ID 要匹配其中一个环境 ID。
事务管理器(transactionManager)
在 MyBatis 中有两种类型的事务管理器(也就是 type="[JDBC|MANAGED]"):
-
JDBC – 这个配置直接使用了 JDBC 的提交和回滚设施,它依赖从数据源获得的连接来管理事务作用域。
-
MANAGED – 这个配置几乎没做什么。它从不提交或回滚一个连接,而是让容器来管理事务的整个生命周期(比如 JEE 应用服务器的上下文)。 默认情况下它会关闭连接。然而一些容器并不希望连接被关闭,因此需要将 closeConnection 属性设置为 false 来阻止默认的关闭行为。
数据源(dataSource)
dataSource 元素使用标准的 JDBC 数据源接口来配置 JDBC 连接对象的资源。
-
大多数 MyBatis 应用程序会按示例中的例子来配置数据源。虽然数据源配置是可选的,但如果要启用延迟加载特性,就必须配置数据源。
有三种内建的数据源类型(也就是 type="[UNPOOLED|POOLED|JNDI]"):
映射器(mappers)
<!-- 将包内的映射器接口实现全部注册为映射器 --> <mappers> <package name="com.han.mapper"/> </mappers> <!-- 使用相对于类路径的资源引用 --> <mappers> <mapper resource="com\han\mapper\UserMapper.xml"/> </mappers> <!-- 使用映射器接口实现类的完全限定类名 --> <mappers> <mapper class="com.han.mapper.UserMapper"/> </mappers>
12.2 XML映射器
-
cache
– 该命名空间的缓存配置。 -
cache-ref
– 引用其它命名空间的缓存配置。 -
resultMap
– 描述如何从数据库结果集中加载对象,是最复杂也是最强大的元素。 -
parameterMap
– 老式风格的参数映射。此元素已被废弃,并可能在将来被移除!请使用行内参数映射。文档中不会介绍此元素。 -
sql
– 可被其它语句引用的可重用语句块。 -
insert
– 映射插入语句。 -
update
– 映射更新语句。 -
delete
– 映射删除语句。 -
select
– 映射查询语句。
Select 元素的属性
属性 | 描述 |
---|---|
id |
在命名空间中唯一的标识符,可以被用来引用这条语句。 |
parameterType |
将会传入这条语句的参数的类全限定名或别名。这个属性是可选的,因为 MyBatis 可以通过类型处理器(TypeHandler)推断出具体传入语句的参数,默认值为未设置(unset)。 |
parameterMap | 用于引用外部 parameterMap 的属性,目前已被废弃。请使用行内参数映射和 parameterType 属性。 |
resultType |
期望从这条语句中返回结果的类全限定名或别名。 注意,如果返回的是集合,那应该设置为集合包含的类型,而不是集合本身的类型。 resultType 和 resultMap 之间只能同时使用一个。 |
resultMap |
对外部 resultMap 的命名引用。结果映射是 MyBatis 最强大的特性,如果你对其理解透彻,许多复杂的映射问题都能迎刃而解。 resultType 和 resultMap 之间只能同时使用一个。 |
flushCache |
将其设置为 true 后,只要语句被调用,都会导致本地缓存和二级缓存被清空,默认值:false。 |
useCache |
将其设置为 true 后,将会导致本条语句的结果被二级缓存缓存起来,默认值:对 select 元素为 true。 |
timeout |
这个设置是在抛出异常之前,驱动程序等待数据库返回请求结果的秒数。默认值为未设置(unset)(依赖数据库驱动)。 |
fetchSize |
这是一个给驱动的建议值,尝试让驱动程序每次批量返回的结果行数等于这个设置值。 默认值为未设置(unset)(依赖驱动)。 |
statementType |
可选 STATEMENT,PREPARED 或 CALLABLE。这会让 MyBatis 分别使用 Statement,PreparedStatement 或 CallableStatement,默认值:PREPARED。 |
resultSetType |
FORWARD_ONLY,SCROLL_SENSITIVE, SCROLL_INSENSITIVE 或 DEFAULT(等价于 unset) 中的一个,默认值为 unset (依赖数据库驱动)。 |
databaseId |
如果配置了数据库厂商标识(databaseIdProvider),MyBatis 会加载所有不带 databaseId 或匹配当前 databaseId 的语句;如果带和不带的语句都有,则不带的会被忽略。 |
resultOrdered |
这个设置仅针对嵌套结果 select 语句:如果为 true,将会假设包含了嵌套结果集或是分组,当返回一个主结果行时,就不会产生对前面结果集的引用。 这就使得在获取嵌套结果集的时候不至于内存不够用。默认值:false 。 |
resultSets |
insert, update 和 delete
描述 | |
---|---|
id |
在命名空间中唯一的标识符,可以被用来引用这条语句。 |
parameterType |
将会传入这条语句的参数的类全限定名或别名。这个属性是可选的,因为 MyBatis 可以通过类型处理器(TypeHandler)推断出具体传入语句的参数,默认值为未设置(unset)。 |
parameterMap |
用于引用外部 parameterMap 的属性,目前已被废弃。请使用行内参数映射和 parameterType 属性。 |
flushCache |
将其设置为 true 后,只要语句被调用,都会导致本地缓存和二级缓存被清空,默认值:(对 insert、update 和 delete 语句)true。 |
timeout |
这个设置是在抛出异常之前,驱动程序等待数据库返回请求结果的秒数。默认值为未设置(unset)(依赖数据库驱动)。 |
statementType |
可选 STATEMENT,PREPARED 或 CALLABLE。这会让 MyBatis 分别使用 Statement,PreparedStatement 或 CallableStatement,默认值:PREPARED。 |
useGeneratedKeys |
(仅适用于 insert 和 update)这会令 MyBatis 使用 JDBC 的 getGeneratedKeys 方法来取出由数据库内部生成的主键(比如:像 MySQL 和 SQL Server 这样的关系型数据库管理系统的自动递增字段),默认值:false。 |
keyProperty |
(仅适用于 insert 和 update)指定能够唯一识别对象的属性,MyBatis 会使用 getGeneratedKeys 的返回值或 insert 语句的 selectKey 子元素设置它的值,默认值:未设置(unset )。如果生成列不止一个,可以用逗号分隔多个属性名称。 |
keyColumn |
(仅适用于 insert 和 update)设置生成键值在表中的列名,在某些数据库(像 PostgreSQL)中,当主键列不是表中的第一列的时候,是必须设置的。如果生成列不止一个,可以用逗号分隔多个属性名称。 |
databaseId |
insert示例
<insert id="insert" parameterType="com.han.pojo.User"> <!-- 获取新增数据后生成的id --> <selectKey keyProperty="id" resultType="Long" order="AFTER" statementType="PREPARED"> select last_insert_id() </selectKey> insert into user(name) value (#{name}) </insert>
描述 | |
---|---|
keyProperty |
selectKey 语句结果应该被设置到的目标属性。如果生成列不止一个,可以用逗号分隔多个属性名称。 |
keyColumn |
返回结果集中生成列属性的列名。如果生成列不止一个,可以用逗号分隔多个属性名称。 |
resultType |
结果的类型。通常 MyBatis 可以推断出来,但是为了更加准确,写上也不会有什么问题。MyBatis 允许将任何简单类型用作主键的类型,包括字符串。如果生成列不止一个,则可以使用包含期望属性的 Object 或 Map。 |
order |
可以设置为 BEFORE 或 AFTER 。如果设置为 BEFORE ,那么它首先会生成主键,设置 keyProperty 再执行插入语句。如果设置为 AFTER ,那么先执行插入语句,然后是 selectKey 中的语句 - 这和 Oracle 数据库的行为相似,在插入语句内部可能有嵌入索引调用。 |
statementType |
和前面一样,MyBatis 支持 STATEMENT ,PREPARED 和 CALLABLE 类型的映射语句,分别代表 Statement , PreparedStatement 和CallableStatement |
字符串替换 ${} #{}
mapper
// 根据输入字段和字段属性查找 User findOne(@Param("column") String column, @Param("obj") Object obj);
映射文件
<select id="findOne" resultType="com.han.pojo.User"> select * from user where ${column} = #{obj} </select>
测试
@Test public void testFindOne(){ UserMapper userMapper = sqlSession.getMapper(UserMapper.class); User user1 = userMapper.findOne("id", 1); System.out.println(user1); User user2 = userMapper.findOne("name", "bbb"); System.out.println(user2); }
输出结果
DEBUG .han.mapper.UserMapper.findOne - ==> Preparing: select * from user where id = ? DEBUG .han.mapper.UserMapper.findOne - ==> Parameters: 1(Integer) DEBUG .han.mapper.UserMapper.findOne - <== Total: 1 User{id=1, name='aaa', roles=null} DEBUG com.han.mapper.UserMapper - Cache Hit Ratio [com.han.mapper.UserMapper]: 0.0 DEBUG .han.mapper.UserMapper.findOne - ==> Preparing: select * from user where name = ? DEBUG .han.mapper.UserMapper.findOne - ==> Parameters: bbb(String) DEBUG .han.mapper.UserMapper.findOne - <== Total: 1 User{id=2, name='bbb', roles=null}
当在映射文件中修改如下
<select id="findOne" resultType="com.han.pojo.User"> select * from user where #{column} = #{obj} </select>
输出结果
DEBUG .han.mapper.UserMapper.findOne - ==> Preparing: select * from user where ? = ? DEBUG .han.mapper.UserMapper.findOne - ==> Parameters: id(String), 1(Integer) DEBUG .han.mapper.UserMapper.findOne - <== Total: 0 null DEBUG com.han.mapper.UserMapper - Cache Hit Ratio [com.han.mapper.UserMapper]: 0.0 DEBUG .han.mapper.UserMapper.findOne - ==> Preparing: select * from user where ? = ? DEBUG .han.mapper.UserMapper.findOne - ==> Parameters: name(String), bbb(String) DEBUG .han.mapper.UserMapper.findOne - <== Total: 0 null
说明#{}修饰的会被转义
注意:用这种方式接受用户的输入,并用作语句参数是不安全的,会导致潜在的 SQL 注入攻击。因此,要么不允许用户输入这些字段,要么自行转义并检验这些参数。
sql
用来定义重复语句
<!-- 定义 --> <sql id = "defaultSql"> select * from user </sql> <!-- 使用 --> <include refid="defaultSql"/>
结果映射(resultMap)
-
constructor
- 用于在实例化类时,注入结果到构造方法中 -
idArg
- ID 参数;标记出作为 ID 的结果可以帮助提高整体性能-
arg
- 将被注入到构造方法的一个普通结果
-
-
id
– 一个 ID 结果;标记出作为 ID 的结果可以帮助提高整体性能 -
result
– 注入到字段或 JavaBean 属性的普通结果 -
association
– 一个复杂类型的关联;许多结果将包装成这种类型 -
嵌套结果映射 – 关联可以是
resultMap
元素,或是对其它结果映射的引用 -
collection
– 一个复杂类型的集合 -
嵌套结果映射 – 集合可以是
resultMap
元素,或是对其它结果映射的引用 -
discriminator
– 使用结果值来决定使用哪个resultMap -
case– 基于某些值的结果映射
-
嵌套结果映射 –
case
也是一个结果映射,因此具有相同的结构和元素;或者引用其它的结果映射
-
<resultMap id="" type="" autoMapping=""> <id column="" property="" javaType="" jdbcType="" typeHandler=""/> <result column="" property="" javaType="" jdbcType="" typeHandler=""/> </resultMap>
属性 | 描述 |
---|---|
id |
当前命名空间中的一个唯一标识,用于标识一个结果映射。 |
type |
类的完全限定名, 或者一个类型别名(关于内置的类型别名,可以参考上面的表格)。 |
autoMapping |
描述 | |
---|---|
property |
映射到列结果的字段或属性。如果 JavaBean 有这个名字的属性(property),会先使用该属性。否则 MyBatis 将会寻找给定名称的字段(field)。 无论是哪一种情形,你都可以使用常见的点式分隔形式进行复杂属性导航。 比如,你可以这样映射一些简单的东西:“username”,或者映射到一些复杂的东西上:“address.street.number”。 |
column |
数据库中的列名,或者是列的别名。一般情况下,这和传递给 resultSet.getString(columnName) 方法的参数一样。 |
javaType |
一个 Java 类的全限定名,或一个类型别名(关于内置的类型别名,可以参考上面的表格)。 如果你映射到一个 JavaBean,MyBatis 通常可以推断类型。然而,如果你映射到的是 HashMap,那么你应该明确地指定 javaType 来保证行为与期望的相一致。 |
jdbcType |
JDBC 类型,所支持的 JDBC 类型参见这个表格之后的“支持的 JDBC 类型”。 只需要在可能执行插入、更新和删除的且允许空值的列上指定 JDBC 类型。这是 JDBC 的要求而非 MyBatis 的要求。如果你直接面向 JDBC 编程,你需要对可以为空值的列指定这个类型。 |
typeHandler |
缓存
<cache/>
-
映射语句文件中的所有 select 语句的结果将会被缓存。
-
映射语句文件中的所有 insert、update 和 delete 语句会刷新缓存。
-
缓存会使用最近最少使用算法(LRU, Least Recently Used)算法来清除不需要的缓存。
-
缓存不会定时进行刷新(也就是说,没有刷新间隔)。
-
缓存会保存列表或对象(无论查询方法返回哪种)的 1024 个引用。
-
缓存会被视为读/写缓存,这意味着获取到的对象并不是共享的,可以安全地被调用者修改,而不干扰其他调用者或线程所做的潜在修改。
提示 缓存只作用于 cache 标签所在的映射文件中的语句。如果你混合使用 Java API 和 XML 映射文件,在共用接口中的语句将不会被默认缓存。你需要使用 @CacheNamespaceRef 注解指定缓存作用域。
十四、逆向工程
功能:读取数据库,生成实体类,mapper类
引入jar包
<!-- https://mvnrepository.com/artifact/mysql/mysql-connector-java --> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>5.1.6</version> </dependency> <!-- https://mvnrepository.com/artifact/org.mybatis.generator/mybatis-generator-core --> <dependency> <groupId>org.mybatis.generator</groupId> <artifactId>mybatis-generator-core</artifactId> <version>1.4.0</version> </dependency> ..... <plugin> <groupId>org.mybatis.generator</groupId> <artifactId>mybatis-generator-maven-plugin</artifactId> <version>1.4.0</version> <configuration> <verbose>true</verbose> <overwrite>true</overwrite> </configuration> </plugin>
配置文件
generator.properties
jdbc.driverLocation=D:\\Maven\\repository\\mysql\\mysql-connector-java\\5.1.6\\mysql-connector-java-5.1.6.jar jdbc.driverClass=com.mysql.jdbc.Driver jdbc.connectionURL=jdbc:mysql:///han jdbc.userId=root jdbc.password=root
generatorConfig.xml
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE generatorConfiguration PUBLIC "-//mybatis.org//DTD MyBatis Generator Configuration 1.0//EN" "http://mybatis.org/dtd/mybatis-generator-config_1_0.dtd"> <generatorConfiguration> <!--导入属性配置--> <properties resource="generator.properties"/> <!--指定特定数据库的jdbc驱动jar包的位置--> <classPathEntry location="${jdbc.driverLocation}"/> <context id="default" targetRuntime="MyBatis3"> <!-- optional,旨在创建class时,对注释进行控制 --> <commentGenerator> <property name="suppressDate" value="true"/> <property name="suppressAllComments" value="true"/> </commentGenerator> <!--jdbc的数据库连接 --> <jdbcConnection driverClass="${jdbc.driverClass}" connectionURL="${jdbc.connectionURL}" userId="${jdbc.userId}" password="${jdbc.password}"> </jdbcConnection> <!-- 非必需,类型处理器,在数据库类型和java类型之间的转换控制--> <javaTypeResolver> <property name="forceBigDecimals" value="false"/> </javaTypeResolver> <!-- Model模型生成器,用来生成含有主键key的类,记录类 以及查询Example类 targetPackage 指定生成的model生成所在的包名 targetProject 指定在该项目下所在的路径 --> <javaModelGenerator targetPackage="com.han.pojo" targetProject="src/main/java"> <!-- 是否允许子包,即targetPackage.schemaName.tableName --> <property name="enableSubPackages" value="false"/> <!-- 是否对model添加 构造函数 --> <property name="constructorBased" value="true"/> <!-- 是否对类CHAR类型的列的数据进行trim操作 --> <property name="trimStrings" value="true"/> <!-- 建立的Model对象是否 不可改变 即生成的Model对象不会有 setter方法,只有构造方法 --> <property name="immutable" value="false"/> </javaModelGenerator> <!--Mapper映射文件生成所在的目录 为每一个数据库的表生成对应的SqlMap文件 --> <sqlMapGenerator targetPackage="com.han.mapper" targetProject="src/main/resources"> <property name="enableSubPackages" value="false"/> </sqlMapGenerator> <!-- 客户端代码,生成易于使用的针对Model对象和XML配置文件 的代码 type="ANNOTATEDMAPPER",生成Java Model 和基于注解的Mapper对象 type="MIXEDMAPPER",生成基于注解的Java Model 和相应的Mapper对象 type="XMLMAPPER",生成SQLMap XML文件和独立的Mapper接口 --> <javaClientGenerator targetPackage="com.han.mapper" targetProject="src/main/java" type="XMLMAPPER"> <property name="enableSubPackages" value="true"/> </javaClientGenerator> <!-- 数据库表名 --> <table tableName="user" enableCountByExample="false" enableUpdateByExample="false" enableDeleteByExample="false" enableSelectByExample="false" selectByExampleQueryId="false"> </table> </context> </generatorConfiguration>
结果
实体类
-------------------------------------------
个性签名:独学而无友,则孤陋而寡闻。做一个灵魂有趣的人!
如果觉得这篇文章对你有小小的帮助的话,记得在右下角点个“推荐”哦,博主在此感谢!
万水千山总是情,打赏一分行不行,所以如果你心情还比较高兴,也是可以扫码打赏博主,哈哈哈(っ•̀ω•́)っ✎⁾⁾!