Mybatis入门
1、概述
Mybatis是一个持久层框架,用java编写
它封装了JDBC操作的很多细节,使开发者只需要关注sql语句本身
而无需关注注册驱动,创建连接等繁杂过程
它使用了ORM思想实现了结果集的封装
ORM:Object Relational Mapping 对象关系映射,就是把数据库表和实体类中的属性对应起来
2、环境搭建
(1)创建Maven工程,配置依赖
//配置Mybatis的jar包依赖,会自动去中央仓库下载jar包
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.4.5</version>
</dependency>
(2)创建JavaBean和DAO的接口
package dao;
import domain.User;
import java.util.List;
/**
* Mybatis允许直接定义以下类型返回值,而不需要在Mapper映射文件中设置resultType属性
* void、Integer、Long、Boolean以及他们的基本数据类型
*/
public interface UserDao {
List<User> findAll();
void addUser(User user);
void updateUser(User user);
Boolean delUser(Integer id);
}
(3)创建Mybatis的主配置文件:SqlMapConfig.xml
<!--Mybatis的主配置文件,该约束是config约束-->
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
<!--配置mybatis的运行环境,数据源、事务等-->
<configuration>
<!-- 和spring整合后 environments配置将废除-->
<!--
1、mybatis可以使用properties标签来引入外部properties位置文件内容
resource属性:引入类路径下的资源
url属性:引入网络路径或磁盘路径下资源
<properties resource="myproperties.properties"></properties>
//若使用该标签引入外部配置文件,property标签中的value属性要使用${key}格式来获取值
//例如 ${jdbc:driver} 获取到的就是myproperties配置文件中的 jdbc:driver对应的值com.mysql.jdbc.Driver
2、settings标签:包含很多重要的设置项
name属性:设置项名
value属性:设置项值
<settings>
<setting name="mapUnderscoreToCamelCase" value="true"/>
// 表示使用驼峰命名规范 user_name <===> userName
</settings>
3、typeAlias标签:为某个java类起别名
1)
<typeAlias type="domain.User" alias="user"></typeAlias>
type属性:指定要起别名的类的全类名,默认别名为类名小写 user
alias:自定义别名
2)
<package name="domain"/> //为该包以及子包下的所有类起默认别名
3)
@Alias("别名") //通过注解形式,为该类起别名
<typeAliases>
<typeAlias type="domain.User" alias="user"></typeAlias>
<package name="domain"/>
</typeAliases>
4、environments:mybatis可以配置多种环境,default指定使用哪一种环境,可以达到快速切换的作用
environment:配置一个具体的环境信息,id表示当前环境的唯一标识。该标签下必须有两个标签
transactionManager:事务管理器
type属性:有两种取值 JDBC\MANAGED
dataSource:数据源
type属性:有三种取值 UNPOOLED\POOLED\JNDI
5、databaseIdProvider:支持多数据库厂商
type="DB_VENDOR" 该属性的作用是得到数据库厂商的标识
<databaseIdProvider type="DB_VENDOR">
//为不同的数据库厂商起别名,之后只需要在Mapper映射文件中的标签中,设置属性databaseId="mysql"。说明该sql语句在哪一个数据库下使用
<property name="Mysql" value="mysql"/>
<property name="Oracle" value="oracle"/>
</databaseIdProvider>
-->
<environments default="mysql">
<environment id="mysql">
<!-- 使用jdbc事务管理-->
<transactionManager type="JDBC"></transactionManager>
<!-- 数据库连接池-->
<dataSource type="POOLED">
<property name="driver" value="com.mysql.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/mybatis"/>
<property name="username" value="root"/>
<property name="password" value="123456"/>
</dataSource>
</environment>
</environments>
<!--指定映射配置文件的位置,映射配置文件指的是每个DAO接口独立的配置文件-->
<mappers>
<mapper resource="dao/UserMapper.xml"/>
</mappers>
</configuration>
(4)创建映射配置文件:UserMapper.xml
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<!--sql的映射文件-->
<!--命名空间,用于隔离sql语句-->
<mapper namespace="dao.UserDao">
<!--
配置UserDao接口中的findAll方法,并说明返回值类型
该接口findAll方法的返回值是一个list集合,但是这里的resultType要写集合中的元素类型
mybatis会自动将这些元素封装到list集合中
-->
<select id="findAll" resultType="domain.User">
select * from user
</select>
<!--配置UserDao接口中的addUser方法,并说明提供的参数类型-->
<!--
mybatis支持获取自增主键的值,原理上也是利用statement.useGeneratedKeys()获取
useGeneratedKeys="true":使用自增主键获取主键值策略
keyProperty="id":mybatis获取到主键后,将这个值封装给javaBean的id属性
order属性:设置该标签体先于父标签体执行,还是后于父标签体执行
resultType:设置该标签体执行后的返回值类型
若主键的值不是自增的,可以使用selectKey标签,设置keyProperty="id",resultType="Integer"
标签体写入查询主键的sql,将查到的值封装为Integer对象,再赋值给了javaBean的id属性
-->
<insert id="addUser" parameterType="domain.User" useGeneratedKeys="false" keyProperty="id">
<!--通过User类中的属性来设置可变参数值-->
insert into user(username,sex,birthday,address) values(#{username},#{sex},#{birthday},#{address});
//该标签会返回一个Integer类型的id,封装到javaBean中的id属性,在父标签之后执行
<selectKey keyProperty="id" order="AFTER" resultType="Integer">
select id from user where username="kon";
</selectKey>
</insert>
<!--配置UserDao接口中的updateUser方法,提供的参数类型可以省略-->
<update id="updateUser">
update user set username=#{username},sex=#{sex},birthday=#{birthday},address=#{address}
where id=#{id};
</update>
<!--配置UserDao接口中的delUser方法-->
<delete id="delUser">
delete from user where id=#{id};
</delete>
</mapper>
环境搭建注意事项:
- 在为每一个dao接口创建映射配置文件Mapper时,名称可以不同,即UserDao.xml和UserMapper.xml是一样的
- Mybatis的映射配置文件位置必须和dao接口的包结构相同
- 映射配置文件的mapper标签namespace属性的取值必须是dao接口的全限定类名
- 映射配置文件的操作配置(select),id属性的值必须是dao接口中的方法名
通过注解配置mapper:
- 在dao接口的方法上使用注解
@Select("select * from user")
List<User> findAll();
- SqlMapConfig.xml中配置该mapper,class值为接口的全类名
<mappers>
<mapper class="dao.UserDao"/>
</mappers>
3、入门案例
//1、读取SqlMapConfig配置文件
InputStream is = Resources.getResourceAsStream("SqlMapConfig.xml");
//2、创建SqlSessionFactory工厂
SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
SqlSessionFactory factory = builder.build(is);
//3、使用工厂生产SqlSession对象,空参的方法默认提交方式为手动提交事务
SqlSession session = factory.openSession();
//4、使用SqlSession创建Dao接口的代理对象
UserDao userdao = session.getMapper(UserDao.class);
//5、使用代理对象执行方法
List<User> users = userdao.findAll();
for (User user : users) {
System.out.println(user);
}
//6、释放资源
session.close();
is.close();
方法详解:
4、自定义mybaits框架(了解mybatis中执行细节)
Mybatis基本使用
单表CRUD操作
(1)添加数据
- dao接口中添加方法
void addUser(User user);
- 配置mapper映射配置文件
<!--配置UserDao接口中的addUser方法,并说明提供的参数类型-->
<insert id="addUser" parameterType="domain.User">
<!--通过User类中的属性来设置可变参数值-->
insert into user(username,sex,birthday,address)
values(#{username},#{sex},#{birthday},#{address});
</insert>
- 使用dao接口的代理对象,执行方法,提交事务
User user=new User();
user.setUsername("zs");
user.setAddress("北京");
user.setBirthday(new Date());
user.setSex("男");
//5、使用代理对象执行方法,记得要提交事务,默认的提交事务方式为手动
proxyuser.addUser(user);
sqlsession.commit();
参数和返回值
- 单个参数:mybatis不会做特殊处理 #{参数名}:取出参数值
- 多个参数:mybatis会做特殊处理,多个参数会被封装成一个map集合
key:param1…paramN
value:传入的参数值
#{param1}:取出第一个参数对应的参数值- 命名参数:多个参数被封装成一个map,明确指定封装参数时map的key
使用注解标注:@Param(“id”) 标注在接口中的每一个参数上
key:该参数被指定的参数名
value:参数值
#{key}:取出对应的参数值
<select id="findByID_Name" resultType="domain.User">
/*
多个参数时,mybatis会将多个参数封装为一个map数组
key=param1,param2...
value=传递的参数值
*/
/*select * from user where id=#{param1} and username=#{param2}*/
/*在Dao接口中的参数上,使用了注解@Param,这样mybatis将多个参数封装为map数组时,key的值=注解中指定的值*/
select * from user where id=#{id} and username=#{name}
</select>
传入何种参数?
- POJO
若多个参数正好是业务逻辑的数据模型,我们可以直接传入pojo(JavaBean)- Map
若多个参数不是业务逻辑中的数据模型,没有对应的pojo,不经常使用,为了方便,也可以直接传入Map- TO
若多个参数不是业务逻辑的数据,但是经常使用,推荐编写一个TO(Transfer Object)数据传输对象
小练习
- public Employee getEmp(@Param(“id”) Integer id,String lastName);
取值:id==>#{id/param1} ,lastName==>#{param2}- public Employee getEmp(Integer id,@Param(“e”)Employee emp);
取值:id==>#{param1} ,lastName==>#{parame2.lastName/e.lastName}- public Employee getEmpById(List< Integer> ids);
取值:id==>#{list[0]/collection[0]}
特别注意
若参数是Collection(List,Set)或者是数组类型,也会特殊处理,即把传入的Collcetion或者数组封装在Map中
- 若参数类型是Collection: key=collection,若是List集合,key还可以等于list
- 若参数类型是数组: key=array
参数的获取
- #{}:是以预编译的形式,将参数设置到sql语句中,类似于PreparedStatement,可以防止SQL注入
- ${}:取出的值直接拼接在SQL上,会有安全问题
- 使用情景:
大多数情况,使用#{};当原生JDBC不支持占位符的地方,我们可以使用${}进行取值,例如,查询年份表,设置排序方式
select * from ${year}_year select * from ${year}_salary order by
salary ${order}
#{}更丰富的用法
可以规定参数的一些规则:jdbcType,mode,numericScale,resultMap,typeHandler,jdbcTypeName,expression(未来准备的功能)
jdbcType通常需要在某些特定情况下被设置:
在我们数据为null时,有些数据库不识别mybatis对null的默认处理。例如Oracle
报错:JdbcTpye OTHER:无效的类型;因为mybatis对所有的null都映射为了OTHER类型
全局配置文件中:jdbcTypeForNull默认为OTHER,oracle不支持
两种解决方式:
1、mapper中:
#{password,jdbcType=NULL};获取password的值,若为null,映射为null
2、SqlMapConfig中:
< setting name=“jdbcTypeForNull” value=“NULL”/>;null类型数据,都映射为null
resultType
该属性中返回的是 期望类型的类的全限定类名或者别名
注意如果是集合,那应该是集合可以包含的类型,而不是集合本身
该属性和resultMap,不能同时使用
resultMap
自定义结果集规范,外部resultMap的命名引用,不能和resultType同时使用
当数据库中的字段和javaBean中的属性对应不上时,可使用自定义结果集规范
<!--
自定义某个javaBean的封装规则
type:自定义规则的java类
id:唯一id方便引用
-->
<resultMap id="myresult" type="domain.User">
<!--指定主键列的封装规则,使用id定义主键会有优化-->
<!--
column:指定数据库的哪一个字段
property:指定对应的javaBean属性
-->
<id column="id" property="id"/>
<!--
定义普通列封装规则,其他不指定的列会自动封装,建议都写上
-->
<result column="username" property="username"/>
<result column="sex" property="sex"/>
</resultMap>
<select id="findAll" resultMap="myresult">
select * from user;
</select>
3、DAO编写
4、配置的细节,几个标签的使用
Mybatis多表查询
1)、association多对一:查询User的同时查询用户对应的部门,涉及到两张表
User–>Department,每一个员工都有对应的部门信息
<resultMap id="UserAndDep" type="domain.User">
<!--级联属性封装结果集,在User类中还有Department类,两者通过d_id和dep_id外键关联-->
<id column="id" property="id"/>
<result column="username" property="username"/>
<result column="sex" property="sex"/>
<result column="birthday" property="birthday"/>
<result column="address" property="address"/>
<result column="d_id" property="d_id"/>
<result column="dep_id" property="department.dep_id"/>
<result column="dep_name" property="department.dep_name"/>
<!--
也可以使用association来指定联合的javaBean对象
property:说明user中的哪一个属性是联合属性
javaType:说明该联合属性的java类型,会自动封装为该类型
-->
<association property="department" javaType="domain.Department">
<id column="d_id" property="dep_id"/>
<result column="dep_name" property="dep_name"/>
</association>
</resultMap>
<select id="findUserAndDepById" resultMap="UserAndDep">
<!--
这条sql查出来的字段有:id,username,sex,birthday,address,d_id,dep_id,dep_name
若使用resultType返回值,无法封装返回值中的信息,这时使用resultMap自定义封装信息
-->
select * from user u,department dep where u.d_id = dep.dep_id and u.id=#{id}
</select>
2)、association多对一进行分步查询:先查询user表中的信息,再根据表中的d_id查询department表中的信息*
<!-- 该mapper为根据部门id查询该部门信息 -->
<mapper namespace="dao.DepartmentDao">
<select id="findById" resultType="domain.Department">
select * from department where dep_id=#{id}
</select>
</mapper>
<resultMap id="selectByStep" type="domain.User">
<!--分步查询,先将查到的user表中的数据封装为一个user对象-->
<id column="id" property="id"/>
<result column="username" property="username"/>
<result column="sex" property="sex"/>
<result column="birthday" property="birthday"/>
<result column="address" property="address"/>
<result column="d_id" property="d_id"/>
<association property="department" select="dao.DepartmentDao.findById" column="d_id">
<!--
将查询到的d_id字段,传递给另一条select标签(是DepartmentMapper下的一个select标签,根据id查找部门表中的信息)
再封装给property指定的属性
property:说明哪一个属性是联合属性,最后会将数据封装到该属性中
select:说明调用哪里的select标签,使用那个标签所在的namespace+id
column:将这一列参数传入select标签中
-->
</association>
</resultMap>
<select id="findById" resultMap="selectByStep">
<!--
这条sql查出来的字段有:id,username,sex,birthday,address,d_id
使用分布查询的方式,先将这些数据封装到User中,再将查到的d_id,去执行另一个select标签
-->
select * from user where id=#{id}
</select>
3)、延迟加载(懒加载、按需加载)
我们每次查询User对象时,Department也会被查出并封装为对象
部门信息在我们使用的时候再去查询
我们可以在分步查询的基础上,添加两个配置
<settings>
<!--设置延迟加载,联合属性的值只有在调用的时候才会查询-->
<setting name="lazyLoadingEnabled" value="true"/>
<setting name="aggressiveLazyLoading" value="false"/>
</settings>
4)、collection一对多: 查询部门表时,查询出该部门的所有用户,涉及两张表;使用了嵌套结果集的方式,使用collection标签定义关联的集合类型的属性封装规则
Department–>User,每一个部门对应多个用户信息
<resultMap id="findUserByDepID" type="domain.Department">
<id column="dep_id" property="dep_id"/>
<result column="dep_name" property="dep_name"/>
<!--
将User表中的字段封装为每一个User对象,并存入List集合,这时只能使用collection,而不能用association
collection可以封装多个对象,association只能封装一个对象
property:说明关联属性
ofType:说明关联属性集合中对应的java类型
-->
<collection property="user" ofType="domain.User">
<id column="id" property="id"/>
<result column="username" property="username"/>
<result column="sex" property="sex"/>
<result column="address" property="address"/>
</collection>
</resultMap>
<select id="findUserByDepID" resultMap="findUserByDepID">
<!--
一对多:一个部门id对应多个User对象
以下sql查询出来的字段包括:department:dep_id,dep_name||user:id,username,sex,address
只能使用自定义结果集,分别封装数据
-->
SELECT d.dep_id,dep_name,u.id,username,sex,address
FROM department d
LEFT JOIN user u
on d.dep_id = u.d_id
where d.dep_id=#{id}
</select>
4)、collection一对多进行分步查询: 先查询department表中的信息,再根据department表中的id,查询user表中所有符合该id的记录
<resultMap id="findUserByStep" type="domain.Department">
<!--
先将查询到的部门信息封装为一个department对象
-->
<id column="dep_id" property="dep_id"/>
<result column="dep_name" property="dep_name"/>
<!--
再根据查询到的dep_id到User表中查询所有符合的记录,封装给property指定的属性
property:指定关联的属性 ==》 department javaBean类中的 List<User> user 属性
-->
<collection property="user" select="dao.UserDao.findUserByDepIdStep" column="dep_id">
</collection>
</resultMap>
<select id="findDepByIdStep" resultMap="findUserByStep">
<!-- 该sql返回的字段有dep_id,dep_name -->
select * from department where dep_id=#{id};
</select>
5)、扩展知识:
<collection property="user" select="dao.UserDao.findUserByDepIdStep" column="{id=dep_id}" fetchType="lazy">
<!--
若要向select中传递多列的值,可以传递一个map
写法:column="{key1=column1,key2=column2}"
此处的key要等于接收方接受的key
因为在select指向的标签中,参数的key=id,所以这里要传入map时,key要指定为id
fetchType:
“lazy” 延迟加载
“eager” 立即加载,而不需要修改全局配置
-->
</collection>
5)、扩展知识discriminator鉴别器: mybatis可以使用discriminator判断某列的值,根据值改变封装行为
<!--
mybatis可以使用discriminator判断某列的值,根据值改变封装行为
例如:若查出的是女生,就把用户信息查询出来封装user对象
若查出的是男生,封装user对象时,将username赋值给email
javaType:列值对应的java类型
column:指定判定的列名
resultType:指定封装的结果类型
-->
<resultMap id="discriminator" type="domain.User">
<discriminator javaType="string" column="sex">
<case value="女" resultType="domain.User">
</case>
<case value="男" resultType="domain.User">
<id column="id" property="id"/>
<result column="username" property="username"/>
<result column="username" property="address"/>
</case>
</discriminator>
</resultMap>
<select id="findById" resultMap="discriminator">
select * from user where id=#{id}
</select>
动态SQL
几种常用标签:
- if:判断;包含test属性
- where:将查询条件包括在内,mybatis就会自动去除第一个多出来的and或者or,防止sql拼接出问题
- choose(when,otherwise):类似于switch case default,只会进入一个分支执行
- trim:用来拼接sql字符串;包含 prefix,profixOverrides,suffix,suffixOverrides属性
- set:将更新的条件放入set中,mybatis会自动去除最后一个多余的逗号
- foreach:可以遍历一个list或者map,取出里面的每一个元素,动态拼接到sql上;包含 collection,item,open,close,separator,index属性
- include:可以引用已经抽取的sql标签中的内容,还可以设置一些属性property,供sql标签中使用,用${}方式来取属性,而不能用#{}
<select id="findUserByDynSql" parameterType="domain.User" resultType="domain.User">
<!--
根据传入的user对象中包含的属性,动态拼装sql
test:写boolean类型的表达式,可以是OGNL表达式
-->
select * from user where
<!-- 此处的id是传入过来的参数中的id属性 -->
<if test="id!=null">
id=#{id}
</if>
<if test="username!=null and username!=''">
and username like #{username}
</if>
<if test="sex!=null">
and sex=#{sex}
</if>
</select>
注意:
查询的时候,若某些条件没带,sql的拼装可能会出问题,例如以上例子中,若不带id,拼接的sql是这样的
select * from user where and username like ? and sex=?
更新的时候,也会出现sql拼装问题,例如
update user set username=?, where id=?
解决方式:
1、给where条件后面加上1=1,以后的条件都有and拼接
2、使用where标签
将所有的查询条件包括在内,mybatis会自动去除第一个多出来的and或者or在where标签中
<select id="findUserByDynSql" parameterType="domain.User" resultType="domain.User">
<!--
根据传入的user对象中包含的属性,动态拼装sql
test:写boolean类型的表达式,可以是OGNL表达式
使用where标签,将查询条件包括在内,mybatis就会自动去除第一个多出来的and或者or,防止sql拼接出问题
也可以给where条件后面加上1=1,以后的条件都有and拼接
-->
select * from user
<where>
<!-- 此处的id是传入过来的参数中的id属性 -->
<if test="id!=null and id!=0">
id=#{id}
</if>
<if test="username!=null and username!=''">
and username like #{username}
</if>
<if test="sex!=null">
and sex=#{sex}
</if>
</where>
</select>
3、使用trim标签
拼接sql的万能方式
<select id="findUserByDynSql" parameterType="domain.User" resultType="domain.User">
<!--
根据传入的user对象中包含的属性,动态拼装sql
test:写boolean类型的表达式,可以是OGNL表达式
使用trim标签,可以在trim包裹的sql字符串上拼接内容
prefix:前缀,在trim包裹的sql字符串上添加前缀
profixOverrides:去掉sql字符串前缀字符
suffix:给sql字符串添加后缀
suffixOverrides:去掉整个sql字符串后面多余的字符
-->
select * from user
<trim prefix="where" prefixOverrides="and" suffix="" suffixOverrides="and">
<!-- 此处的id是传入过来的参数中的id属性 -->
<if test="id!=null and id!=0">
id=#{id} and
</if>
<if test="username!=null and username!=''">
username like #{username} and
</if>
<if test="sex!=null">
sex=#{sex} and
</if>
</trim>
</select>
4、使用choose标签
该标签类似于switch case,只会进入某一分支执行
<where>
<choose>
<when test="id!=null and id!=0">
id=#{id}
</when>
<when test="username!null and username!=''">
username=#{username}
</when>
<otherwise>1=1</otherwise>
</choose>
</where>
5、使用set标签
将要更新的条件放入改标签体中,mybatis会自动去除多余的逗号
<!--
动态拼接更新数据的sql语句,将要更新的条件放入set标签中,mybatis会自动去除多余的逗号
-->
<update id="updateUser" parameterType="domain.User">
update user
<set>
<if test="username!=null and username!=''">
username=#{username},
</if>
<if test="sex!=null and sex!=''">
sex=#{sex},
</if>
</set>
where id=#{id}
</update>
6、使用foreach标签
可以遍历传入的参数值,动态生成sql语句
<!--
动态拼接sql,根据传入的id集合,查询记录
collection:指定要遍历的集合,list类型集合会特殊处理封装在map中,map的key就叫list
item:将遍历出的元素赋值给指定变量
separator:每个元素之间的分隔符
open:在标签体执行之前,拼接一个指定字符
close:在标签体执行结束后,拼接一个指定字符
index:遍历list的时候,index就是索引,item就是值
遍历map的时候,index就是map的key,item就是map的value
#{变量名}:就能取出变量的值即当前遍历出的元素
-->
<select id="findUserByIds" parameterType="list" resultType="domain.User">
select * from user where id in
<!-- 遍历传入的list集合 -->
<foreach collection="list" item="item_id" open="(" separator="," close=")">
#{item_id}
</foreach>
</select>
<!--
动态拼接sql,根据传入的user集合,添加记录
mysql支持 values(),(),()的语法来批量添加记录
-->
<insert id="addUsers">
insert into user (username,sex,birthday,address,d_id)
values
<foreach collection="users" item="user" separator=",">
(#{user.username},#{user.sex},#{user.birthday},#{user.address},#{user.d_id})
</foreach>
</insert>
<!--
第二种批量操作方式
不仅可以批量添加,还可以批量删除,更新等等,sql语句之间用分号分隔
这样需要设置数据库连接属性allowMultiQueries=true 允许多个sql语句执行
-->
<insert id="addUsers">
<foreach collection="users" item="user" separator=";">
insert into user (username,sex,birthday,address,d_id)
values(#{user.username},#{user.sex},#{user.birthday},#{user.address},#{user.d_id})
</foreach>
</insert>
7、内置参数_parameter&_databaseId
mybatis在传递参数时,会再传递两个参数
- _parameter:代表整个参数
- 单个参数:_parameter就是这个参数
- 多个参数:参数会被封装为map集合,_parameter就是这个map
- _databaseId:若配置了databaseIdProvider标签
_databaseId就代表当前数据库的别名
<foreach collection="users" item="user" separator=",">
<if test="_parameter!=null and _database=='mysql'">
(#{user.username},#{user.sex},#{user.birthday},#{user.address},#{user.d_id})
</if>
</foreach>
8、使用include标签
引用被sql标签抽取的sql片段,还可以设置一些property,供sql标签使用
<!--
动态拼接sql,根据传入的user集合,添加记录
mysql支持 values(),(),()的语法来批量添加记录
include标签:可以引入外部sql,可以自定义一些property,供外部sql标签使用
-->
<insert id="addUsers">
insert into user
<include refid="parameter">
<property name="d_id" value="d_id"/>
</include>
values
<foreach collection="users" item="user" separator=",">
(#{user.username},#{user.sex},#{user.birthday},#{user.address},#{user.d_id})
</foreach>
</insert>
<!--抽取sql片段,可以调用include标签中定义的property属性,只能使用${},而不能使用#{}-->
<sql id="parameter">
(username,sex,birthday,address,${d_id})
</sql>
Mybatis缓存和注解开发
一级缓存和二级缓存
一级缓存(本地缓存)
1、概念
SqlSession级别的缓存,该缓存一直是开启的
与数据库同一次会话期间查询到的数据会放在本地缓存中
以后若需要获取相同数据,直接从缓存中拿,不用再发sql语句查询数据库
2、一级缓存失效情况(即没有使用到缓存中的数据,再次向数据库发送了sql)
- sqlSession不同
- sqlSession相同,查询条件不同,即当前一级缓存中没有该数据
- sqlSession相同,两次查询期间,进行了增删改操作(这些操作可能对数据有影响)
- sqlSession相同,手动清除了一级缓存 sqlSession.clearCache()
二级缓存(全局缓存)
1、概念
基于namespace级别的缓存,一个namespace对应一个二级缓存
工作机制:
- 一个会话,查询一条记录,这个数据会被放在当前会话的一级缓存中
- 若会话关闭,一级缓存中的数据保存到二级缓存中,新的会话查询可以参照二级缓存中的数据
- sqlSession
=== UserMapper = =>User
=== DepartmentMapper= =>Department
不同的namespace查出的数据会放在自己对应缓存中
2、使用
1、 在全局配置文件中设置为开启二级缓存
< setting name=“cacheEnabled” value=“true”/>
2、在mapper.xml文件中加入< cache >标签
cache标签中的几个属性:
- eviction:缓存的回收策略
1. LRU 最近最少未使用,移除最长时间不被使用的对象。默认值
2. FIFO 先进先出,按对象进入缓存的顺序来移除
3. SOFT 软引用,移除基于垃圾回收器状态和软引用规则的对象
4. WEAK 弱引用,更积极的移除基于收集器状态和弱引用规则的对象- flushInterval:缓存刷新间隔
缓存多长时间清空一次,单位毫秒值;默认不清空- readOnly:是否只读
1. true:mybatis认为所有从缓存中获取数据的操作都是只读操作,不会修改数据 为了加快获取速度,直接将该数据的引用交给用户;不完全,速度快
2. false:默认值;mybatis认为获取的数据可能会被修改,会使用序列化&反序列化的技术,克隆一份新的数据给用户;安全,速度慢- size:说明该缓存最大容量上限
- type:使用自定义的缓存机制,值为全类名;自己写一个类,实现cache接口
3、我们的POJO需要实现序列化接口
因为有可能涉及到javaBean的序列化操作
User user1 = proxyuser.findById(1);
SqlSession session2 = factory.openSession();
UserDao proxyuser2 = session2.getMapper(UserDao.class);
//此时sqlsession被关闭,一级缓存中数据保存到二级缓存中
session.close();
//此处拿到的对象是mybatis从二级缓存中克隆了一份新的数据,所以user1!=user2
User user2 = proxyuser2.findById(1);
System.out.println(user1==user2);
注意:
二级缓存若要被使用,必定是在一次会话关闭后,一级缓存中的数据保存到二级缓存
4、和缓存有关的设置/属性
- cacheEnabled=true/false 开启/关闭二级缓存
- 每个select标签中都有useCache=true/false 使用/不使用二级缓存
- 每个增删改查标签中都有flushCache=true/false 增删改查操作执行后是否清除缓存,增删改默认清除,查询标签默认不清除
- sqlSession.clearCache();只是清除当前sqlSession的缓存
- localCacheScope;本地缓存作用域,默认值SESSION,即一级缓存,可以设置为STATEMENT,即禁用了一级缓存
5、缓存原理图示
Mybatis逆向工程
- Mybatis Generator:简称MBG,是一个专门为mybatis框架使用者定制的代码生成器,可以快速的根据表生成对应的映射文件,接口,以及javabean类。支持基本的增删改查、以及QBC风格的条件查询。但是表连接、存储过程等复杂sql的定义需要手工编写
- 官方文档地址
http://www.mybatis.org/generator/ - 官方工程地址
http://github.com/mybatis/generator/releases
Mybatis运行原理
1、获取SqlSessionFactory对象
解析文件的每一个信息保存在Configuration对象中,返回包含Configuration对象的DefaultSqlSessionFactory对象
2、获取SqlSession对象
返回一个DefaultSqlSession对象,该对象中包含了Executor和Configuration对象
这一步会创建Executor对象
3、获取接口的代理对象(MapperProxy)
getMapper,使用MapperProxyFactory创建一个MapperProxy的代理对象
该对象中包含了,DefaultSqlsession(Executor)
4、执行接口中的的方法
运行原理总结
- 根据配置文件(全局,sql映射)初始化出Configuration对象
- 创建一个DefaultSqlSession对象
它里面包含Configuration以及
Executor(根据全局配置文件中的defaultExecutorType创建对应的executor)- DefaultSqlSession.getMapper();拿到Mapper接口对应的MapperProxy对象
MapperProxy对象里面有(DefaultSqlSession)- 执行增删改查方法
- 调用DefaultSqlSession的增删改查(Executor)
- 创建一个StatementHandler对象(同时也会创建出ParameterHandler和ResuletSetHandler)
- 调用StatementHandler预编译参数以及设置参数值,使用ParameterHandler来给sql设置参数
- 调用StatementHandler的增删改查方法
- ResultSetHandler封装结果