一:配置文件解析
SqlMapConfig.xml:
1、environments标签
2、mapper标签
通常用来加载mapper.xml, 当然也可以加载Mappper接口等等。 用法如下
• 使⽤相对于类路径的资源引⽤,例如:<mapper resource="org/mybatis/builder/AuthorMapper.xml"/>• 使⽤完全限定资源定位符( URL ),例如:<mapper url="file:///var/mappers/AuthorMapper.xml"/>• 使⽤映射器接⼝实现类的完全限定类名,例如:<mapper class="org.mybatis.builder.AuthorMapper"/>• 将包内的映射器接⼝实现全部注册为映射器,例如:<package name="org.mybatis.builder"/>
将db配置信息单独存放在properties文件,然后使用该标签加载使用
4、typeAliases标签
取别名的。比如:
常用的java类也有对应的别名
Mapper.xml:
动态映射sql语句,入参和返回值。 常规增删改查标签和简单的单表的写法这里就不说了。
说一下复杂映射:
一对一
Order中有User:
public class Order { private Integer id; private String orderTime; private Double total; // 表明该订单属于哪个用户 private User user; }
<mapper namespace="com.lagou.mapper.IOrderMapper"> <resultMap id="orderMap" type="com.lagou.pojo.Order"> <result property="id" column="id"></result> <result property="orderTime" column="orderTime"></result> <result property="total" column="total"></result> <association property="user" javaType="com.lagou.pojo.User"> <result property="id" column="uid"></result> <result property="username" column="username"></result> </association> </resultMap> <!--resultMap:手动来配置实体属性与表字段的映射关系--> <select id="findOrderAndUser" resultMap="orderMap"> select * from orders o,user u where o.uid = u.id </select> </mapper>
一对多
一个user有多个订单
public class User { private int id; private String username; private String password; private Date birthday; //代表当前⽤户具备哪些订单 private List<Order> orderList; }
<resultMap id="userMap" type="com.lagou.pojo.User"> <result property="id" column="uid"></result> <result property="username" column="username"></result> <collection property="orderList" ofType="com.lagou.pojo.Order"> <result property="id" column="id"></result> <result property="orderTime" column="orderTime"></result> <result property="total" column="total"></result> </collection> </resultMap> <select id="findAll" resultMap="userMap"> select * from user u left join orders o on u.id = o.uid </select>
多对多
一个用户多个角色,一个角色属于多个用户
public class Role { private Integer id; private String roleName; private String roleDesc;}
<resultMap id="userRoleMap" type="com.lagou.pojo.User"> <result property="id" column="userid"></result> <result property="username" column="username"></result> <collection property="roleList" ofType="com.lagou.pojo.Role"> <result property="id" column="roleid"></result> <result property="roleName" column="roleName"></result> <result property="roleDesc" column="roleDesc"></result> </collection> </resultMap> <select id="findAllUserAndRole" resultMap="userRoleMap"> select * from user u left join sys_user_role ur on u.id = ur.userid left join sys_role r on r.id = ur.roleid </select>
其实resultMap 就是一对多的方写法。 因为站着user或者role的角度上,都是一对多。
上面都是mapper.xml的配置文件开发,mybatis还支持注解开发。
二:注解开发
注解要在Mapper接口上写,所以在SqlMapConfig.xml中,需要引入的是Mapper接口而不是mapper.xml。
<mappers><!--扫描使⽤注解的类所在的包 --><package name = "com.lagou.mapper" ></package></mappers>
User相关查询:
public interface IUserMapper { //一对多:查询所有用户、同时查询每个用户关联的订单信息 @Select("select * from user") @Results({ @Result(property = "id",column = "id"), @Result(property = "username",column = "username"), @Result(property = "orderList",column = "id",javaType = List.class, many=@Many(select = "com.lagou.mapper.IOrderMapper.findOrderByUid")) }) public List<User> findAll(); //多对多:查询所有用户、同时查询每个用户关联的角色信息 @Select("select * from user") @Results({ @Result(property = "id",column = "id"), @Result(property = "username",column = "username"), @Result(property = "roleList",column = "id",javaType = List.class, many = @Many(select = "com.lagou.mapper.IRoleMapper.findRoleByUid")) }) public List<User> findAllUserAndRole(); //添加用户 @Insert("insert into user values(#{id},#{username})") public void addUser(User user); //更新用户 @Update("update user set username = #{username} where id = #{id}") public void updateUser(User user); //查询用户 @Select("select * from user") public List<User> selectUser(); //删除用户 @Delete("delete from user where id = #{id}") public void deleteUser(Integer id); //根据id查询用户 @Options(useCache = true) @Select({"select * from user where id = #{id}"}) public User findUserById(Integer id); }
Order相关查询:
public interface IOrderMapper {
//一对一:查询订单的同时还查询该订单所属的用户
@Results({
@Result(property = "id",column = "id"),
@Result(property = "orderTime",column = "orderTime"),
@Result(property = "total",column = "total"),
@Result(property = "user",column = "uid",javaType = User.class,
one=@One(select = "com.lagou.mapper.IUserMapper.findUserById"))
})
@Select("select * from orders")
public List<Order> findOrderAndUser();
@Select("select * from orders where uid = #{uid}")
public List<Order> findOrderByUid(Integer uid);
}
三:缓存
1、一级缓存(sqlSession级别的缓存)
使用同一个sqlSession执行多次相同sql查询, 只有第一次会请求数据库。 当然,如果sqlSession对同一数据有写操作并提交了事务,会清空缓存。
既然是sqlSession级别的缓存,查找sqlSession的方法,只有一个在操作缓存:
clearCache();
依次跟踪发现调用链:
底层是Perpetualcache, 点进去发现缓存就是一个hashmap,也就是调用map.clear()方法。
每个sqlSession都会为其创建一个本地的map作为缓存,是在执行器Executor中,调用BaseExecutor的createCacheKey方法创建的:
经过几个update方法把每一项信息放到了updateList中,确定了唯一。 缓存什么时候使用呢?
在 List<E>查询方法中,先从缓存取,没有就查数据库,再放到缓存中 。
2、二级缓存:
二级缓存就是针对同一个namespace下,也就是同一个mapper.xml中,多个sqlSession共享同一个sql的缓存(sqlSession动态代理创建mapper,也就是多个mapper共享)。
当然,如果过程中某一个mapper执行了commit,会清空缓存。
二级缓存需要手动开启:
在全局sqlMapConfig.xml中
<!--开启⼆级缓存-->
<settings><setting name = "cacheEnabled" value = "true" /></settings>
在某个mapper.xml中
<!-- 开启⼆级缓存 --><cache></cache>
看起来是个空标签,默认使用了PerpetualCache
a、实现了Cache接口,我们也可以实现该接口自定义缓存,在typemapper.xml中就不再是空标签了,需要type指定。
b、二级缓存也是个hashmap。
c、对缓存的实体需要实现Serializable,因为二级缓存不一定只存储在内存中,还可以存在redis或者其他磁盘介质,就需要序列化与反序列化了。 比如,我们使用type指定二级缓存使用Redis来存储。
<cache type = "org.mybatis.caches.redis.RedisCache" />
另外,还可以对某个statement禁用使用缓存或者刷新缓存:
useCache为false代表该语句每次都查数据库,不会使用二级缓存(默认true);
flushCache为false代表commit后,不会刷新缓存,会产生脏读(默认true);
四:插件
mybatis对持久层的操作,主要是借助四个核心对象:
执⾏器 Executor:update、 query 、 commit 、 rollback 等⽅法 ;SQL 语法构建器 StatementHandler :prepare、 parameterize 、 batch 、 updates query 等⽅ 法 ;参数处理器 ParameterHandler :getParameterObject、 setParameters ⽅法 ;结果集处理器 ResultSetHandler :handleResultSets、 handleOutputParameters 等⽅法 ;
当然,并不是直接使用这几个对象操作,而是通过很多插件对这几个对象,通过jdk动态代理生成代理对象,对方法进行扩展。
这些插件就是一系列的拦截器(注意:是mybatis自己的拦截器,不是springmvc的拦截器)。
比如,对ParameterHandler的使用,拦截器是如何工作的:
首先,容器启动时,mybatis会将所有的拦截器读取,并设置到全局配置类Configuration的拦截器链中。
其次,Configuration中提供了创建ParameterHandler的方法
如上,拦截器连中每个拦截器,都调用了plugin方法对target做处理。
Plugin是一个实现了InvocationHandler的类,也就是JDK动态代理的实现
wrap方法中,会判断当前interceptor中,是否对当前target做拦截,如果是,创建target的代理对象;如果否,直接返回原生的target。
假如这时候有一个拦截器,配置了拦截ParameterHandler的方法,这里就会对ParameterHandler生成代理对象。 后续调用方法时,就会进入invoke方法。 invoke方法中,又调用了当前interceptor的intercept方法,实现增强:
这时候,如果拦截器链中多个拦截器都生效,最后一个拦截器处理后,生成了一个经过层层代理的终极代理对象:
总结来说:
1、全局配置类提供了创建四大对象的方法。
2、在具体创建某个对象时,会调用所有interceptor的wrap方法,如果某个interceptor发现自己需要拦截当前目标对象,就为目标创建代理。
3、代理调用方法时,通过invoke回调到interceptor的intercept方法中,实现逻辑的增强。
下面自定义一个拦截器:
import org.apache.ibatis.executor.statement.StatementHandler; import org.apache.ibatis.plugin.*; import java.sql.Connection; import java.util.Properties; @Intercepts({ //注意看这个⼤花括号,也就这说这⾥可以定义多个@Signature对多个地⽅拦截,都⽤这个拦截器 @Signature (type = StatementHandler .class , //这是指拦截哪个接⼝ method = "prepare", //这个接⼝内的哪个⽅法名,不要拼错了 args = { Connection.class, Integer .class}), // 这是拦截的⽅法的⼊参,按顺序写。如果⽅法重载,要通过⽅法名和⼊参来确定 }) public class MyPlugin implements Interceptor { @Override public Object intercept(Invocation invocation) throws Throwable { //增强逻辑 System.out.println("对⽅法进⾏了增强...."); //执⾏原⽅法 return invocation.proceed(); } @Override public Object plugin(Object target) { System.out.println("⽬标对象:" + target); return Plugin.wrap(target, this); } @Override public void setProperties(Properties properties) { System.out.println("插件配置的初始化参数:" + properties); } }
其实就是实现了mybatis中的Interceptor接口,重写逻辑。 并在类上使用@Intercepts注解配置要拦截的类和方法。
将Myplugin配置到配置文件中:
<plugins><plugin interceptor = "com.example.demo.interceptor.MyPlugin " ><!--配置参数 --><property name = "name" value = "Bob" /></plugin></plugins>
其实mybatis中有两个插件会经常使用。 分页插件PageHelper和通用插件Mapper
一个是分页用的,一个是提供mapper基本的增删改查方法。 具体使用这里不说了。