mybatis学习笔记(二)(mybatis的使用以及相关)

mybatis学习笔记(二)

1.myBatis相关概念

ORM全称Object/Relation Mapping:表示对象-关系映射的缩写

Mybatis是⼀个半⾃动化的持久层框架,对开发⼈员开说,核⼼sql还是需要⾃⼰进⾏优化,sql和java编
码进⾏分离,功能边界清晰,⼀个专注业务,⼀个专注数据。

hibernate:全自动的 不能对sql优化


2.环境搭建

1).引入依赖

 	<properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <maven.compiler.encoding>UTF-8</maven.compiler.encoding>
        <java.version>11</java.version>
        <maven.compiler.source>11</maven.compiler.source>
        <maven.compiler.target>11</maven.compiler.target>
    </properties>
    <dependencies>
        <!--mybatis坐标-->
        <dependency>
            <groupId>org.mybatis</groupId>
            <artifactId>mybatis</artifactId>
            <version>3.4.5</version>
        </dependency>
        <!--mysql驱动坐标-->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>5.1.6</version>
            <scope>runtime</scope>
        </dependency>
        <!--单元测试坐标-->
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.12</version>
            <scope>test</scope>
        </dependency>
        <!--⽇志坐标-->
        <dependency>
            <groupId>log4j</groupId>
            <artifactId>log4j</artifactId>
            <version>1.2.12</version>
        </dependency>
    </dependencies>

2).文件头

普通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">

sqlMapConfig

<!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
  <configuration>
      <environments default="development">
          <environment id="development">
              <transactionManager type="JDBC"/>
              <dataSource type="POOLED">
                  <property name="driver" value="com.mysql.jdbc.Driver"/>
                  <property name="url" value="jdbc:mysql:///test"/>
                  <property name="username" value="root"/>
                  <property name="password" value="root"/>
              </dataSource>
          </environment>
      </environments>
      <mappers>
         <mapper resource="com/lagou/mapper/UserMapper.xml"/>
      </mappers>
</configuration>

3).启动时遇到问题

1.无效的 XML 字符

​ 在复制pdf中的代码时,双引号转移成错误的字符 修改为正确的即可

2.Could not find resource

​ 复制代码 配置环境不一致


3.mybatis的CRUD操作

​ 不同的操作使用不同的标签

 public void  test4() throws IOException {
        InputStream resourceAsStream = Resources.getResourceAsStream("sqlMapConfig.xml");
        SqlSessionFactory build = new SqlSessionFactoryBuilder().build(resourceAsStream);
        SqlSession sqlSession = build.openSession();
        User user = new User();
        user.setId("3");
        user.setName("wangwu");
        user.setSex("2");
        user.setAge(11);
        //sqlSession.selectList("user.findAll");
        //sqlSession.insert("user.saveUser",user);
     	//sqlSession.update("user.updateUser",user);
        sqlSession.delete("user.deleteUser",3);
        sqlSession.commit();
        sqlSession.close();
    }

​ 操作涉及数据库数据变化,要使⽤sqlSession对象显示的提交事务,即sqlSession.commit()


4.相关配置文件

在这里插入图片描述

标签层级示意图

在这里插入图片描述

​ sqlMapConfig.xml

<!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">

  <configuration>
    <!--加载外部的properties文件-->
    <properties resource="jdbc.properties"></properties>
    <!--typeAliases 给实体类的全限定类名起别名-->
    <typeAliases>
        <!--给单独的实体-->
       <!-- <typeAlias type="com.zmx.pojo.User" alias="user"></typeAlias>-->
        <!--批量起别名  别名不区分大小写-->
        <package name="com.zmx.pojo"/>
    </typeAliases>
    <!--environments 运行环境-->
    <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>
      <mappers>
         <mapper resource="UserMapper.xml"/>
      </mappers>
</configuration>

1). environments标签

数据库环境的配置,⽀持多环境配置

事务管理器(transactionManager)类型有两种:
•JDBC:这个配置就是直接使⽤了JDBC 的提交和回滚设置,它依赖于从数据源得到的连接来管理事务作
⽤域。
•MANAGED:这个配置⼏乎没做什么。它从来不提交或回滚⼀个连接,⽽是让容器来管理事务的整个⽣
命周期(⽐如 JEE 应⽤服务器的上下⽂)。 默认情况下它会关闭连接,然⽽⼀些容器并不希望这样,因
此需要将 closeConnection 属性设置为 false 来阻⽌它默认的关闭⾏为。
其中,数据源(dataSource)类型有三种:
•UNPOOLED:这个数据源的实现只是每次被请求时打开和关闭连接。
•POOLED:这种数据源的实现利⽤“池”的概念将 JDBC 连接对象组织起来。

•JNDI:这个数据源的实现是为了能在如 EJB 或应⽤服务器这类容器中使⽤,容器可以集中或在外部配
置数据源,然后放置⼀个 JNDI 上下⽂的引⽤。

2).mapper标签

加载映射的 加载⽅式有如下⼏种:

•使⽤相对于类路径的资源引⽤,例如:
<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"/>

3).Properties标签

​ 将数据源的配置信息单独抽取成⼀个properties⽂件,该标签可以加载额外配置的 properties⽂件

​ jdbc.properties

jdbc.driver=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql:///zmx_mybatis
jdbc.username=root
jdbc.password=root
<!--加载外部的properties文件-->
<properties resource="jdbc.properties"></properties>


<property name="driver" value="${jdbc.driver}"/>
<property name="url" value="${jdbc.url}"/>
<property name="username" value="${jdbc.username}"/>
<property name="password" value="${jdbc.password}"/>

4). typeAliases标签

​ 为Java 类型设置⼀个短的名字

 <!--typeAliases 给实体类的全限定类名起别名-->
    <typeAliases>
        <!--给单独的实体-->
       <!-- <typeAlias type="com.zmx.pojo.User" alias="user"></typeAlias>-->
        <!--批量起别名  别名不区分大小写-->
        <package name="com.zmx.pojo"/>
    </typeAliases>

5.相关api

//1.Resources工具类 配置文件加载 将配置文件加载成字节输入流
InputStream resourceAsStream = Resources.getResourceAsStream("sqlMapConfig.xml");
//2.解析配置文件 创建SqlSessionFactory工厂
SqlSessionFactory build = new SqlSessionFactoryBuilder().build(resourceAsStream);
//3.生产SqlSession
SqlSession sqlSession = build.openSession();//默认开启一个事务 但是该事务不会自动提交
// SqlSession sqlSession = build.openSession(加入参数时会自定提交);  //在进行增删改操作时  手动提交事务
//4. sqlSession调用方法 查询所有-selectList 查询单个-selectOne 添加-add   修改-update  删除-delete
List<User> objects = sqlSession.selectList("user.findAll");

SqlSession⼯⼚构建器 SqlSessionFactoryBuilder

常⽤API:SqlSessionFactory build(InputStream inputStream)
通过加载mybatis的核⼼⽂件的输⼊流的形式构建⼀个SqlSessionFactory对象

Resources ⼯具类,这个类在 org.apache.ibatis.io 包中。Resources 类帮助你从类路径下、⽂
件系统或⼀个 web URL 中加载资源⽂件。

SqlSession⼯⼚对象 SqlSessionFactory

SqlSessionFactory 有多个个⽅法创建SqlSession 实例

SqlSession sqlSession = build.openSession();//默认开启一个事务 但是该事务不会自动提交  手动提交事务
SqlSession sqlSession = build.openSession(true);  //在进行增删改操作时 加入参数时会自定提交

SqlSession会话对象

SqlSession 实例在 MyBatis 中是⾮常强⼤的⼀个类。在这⾥你会看到所有执⾏语句、提交或回滚事务
和获取映射器实例的⽅法。

<T> T selectOne(String statement, Object parameter)
<E> List<E> selectList(String statement, Object parameter)
int insert(String statement, Object parameter)
int update(String statement, Object parameter)
int delete(String statement, Object parameter)

操作事务

void commit()
void rollback()

6-7 dao层开发方式

1).传统开发方式

编写dao层接口以及Impl实现类

public interface UserDao {
	List<User> findAll() throws IOException;
}

public class UserDaoImpl implements UserDao {
    public List<User> findAll() throws IOException {
        InputStream resourceAsStream =  Resources.getResourceAsStream("SqlMapConfig.xml");
        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(resourceAsStream);
        SqlSession sqlSession = sqlSessionFactory.openSession();
        List<User> userList = sqlSession.selectList("userMapper.findAll");
        sqlSession.close();
        return userList;
    }
}

2).代理开发方式

采⽤ Mybatis 的代理开发⽅式实现 DAO 层的开发。
Mapper 接⼝开发⽅法只需要编写Mapper 接⼝(相当于Dao 接⼝),由Mybatis 框架根据接⼝
定义创建接⼝的动态代理对象,代理对象的⽅法体同上边Dao接⼝实现类⽅法。
Mapper 接⼝开发需要遵循以下规范:
1) Mapper.xml⽂件中的namespace与mapper接⼝的全限定名相同
2) Mapper接⼝⽅法名和Mapper.xml中定义的每个statement的id相同
3) Mapper接⼝⽅法的输⼊参数类型和mapper.xml中定义的每个sql的parameterType的类型相同
4) Mapper接⼝⽅法的输出参数类型和mapper.xml中定义的每个sql的resultType的类型相同

在这里插入图片描述

@Test
public void testProxyDao() throws IOException {
        InputStream resourceAsStream =
        Resources.getResourceAsStream("SqlMapConfig.xml");
        SqlSessionFactory sqlSessionFactory = new
        SqlSessionFactoryBuilder().build(resourceAsStream);
        SqlSession sqlSession = sqlSessionFactory.openSession();
        //获得MyBatis框架⽣成的UserMapper接⼝的实现类
        UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
        User user = userMapper.findById(1);
        System.out.println(user);
	   sqlSession.close();
}

8-9 TypeAliases标签-Properties标签

​ 略 (详见 4 相关配置文件)


10-11.动态标签

1). if 标签

根据实体类的不同取值,使⽤不同的 SQL语句来进⾏查询

<select id="findByCondition" resultType="user" parameterType="user">
        <include refid="selectUser"></include>
        <where>
            <if test="id != null">
                and id=#{id}
            </if>
            <if test="name != null">
                and name=#{name}
            </if>
        </where>
</select>

2). foreach标签

循环执⾏sql的拼接操作

foreach标签的属性含义如下:
标签⽤于遍历集合,它的属性:
•collection:代表要遍历的集合元素,注意编写时不要写#{}
•open:代表语句的开始部分
•close:代表结束部分
•item:代表遍历集合的每个元素,⽣成的变量名
•sperator:代表分隔

<select id="findByIds" resultType="user" parameterType="list">
        <include refid="selectUser"></include>
        <where>
            <foreach collection="array"  open="id in(" close=")" item="id" separator=",">
                #{id}
            </foreach>
        </where>
</select>

3). 片段抽取

Sql 中可将重复的 sql 提取出来,使⽤时⽤ include 引⽤

 <!--抽取sql片段-->
    <sql id="selectUser">
        select * from user
    </sql>
    <select id="findAll" resultType="user">
        <include refid="selectUser"></include>
    </select>


12-14.mybatis复杂映射

1).一对一查询:

​ 创建实体类

public class Order {
     private int id;
     private Date ordertime;
     private double total;
     //代表当前订单从属于哪⼀个客户
     private User user;
}
public class User {
     private int id;
     private String username;
     private String password;
     private Date birthday;
}

​ mapper配置文件(两种方式同样效果)

	<resultMap id="orderMap" type="com.zmx.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.zmx.pojo.User">
            <result property="id" column="uid"></result>
            <result property="username" column="username"></result>
        </association>

    </resultMap>

    <!--resultMap 手动配置实体属性与表字段的映射关系-->
    <select id="findOrderUser" resultMap="orderMap">
        select * from orders o,user u  where o.uid = u.id
    </select>

<resultMap id="orderMap" type="com.lagou.domain.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.domain.User">
         <result column="uid" property="id"></result>
         <result column="username" property="username"></result>
         <result column="password" property="password"></result>
         <result column="birthday" property="birthday"></result>
     </association>
</resultMap>

其他:引进mapper文件的方式

	 <mappers>
         <package name="com.zmx.mapper"/>
      </mappers>

​ 需要在resource文件目录下边创建与对应实体类包结构相同的文件结构

2).一对多查询

​ 创建实体类(单个用户对应多个账单)

public class Order {
     private int id;
     private Date ordertime;
     private double total;
     //代表当前订单从属于哪⼀个客户
     private User user;
}
public class User {
 
     private int id;
     private String username;
     private String password;
     private Date birthday;
     //代表当前⽤户具备哪些订单
     private List<Order> orderList;
}

mapper配置文件

 <resultMap id="userMap" type="com.zmx.pojo.User">
        <id property="id" column="id"></id>
        <result property="username" column="username"></result>

        <collection property="orderList"  ofType="com.zmx.pojo.Order">
            <id property="id" column="oid"></id>
            <result property="orderTime" column="orderTime"></result>
            <result property="total" column="total"></result>
        </collection>

    </resultMap>

    <!--resultMap 手动配置实体属性与表字段的映射关系-->
    <select id="findAll" resultMap="userMap">
        select *,o.id oid from user u left join orders o on u.id=o.uid
    </select>

3). 多对多查询

​ 创建实体类

public class User {
    private int id;
    private String username;
    private String password;
    private Date birthday;
    //代表当前⽤户具备哪些订单
    private List<Order> orderList;
    //代表当前⽤户具备哪些⻆⾊
    private List<Role> roleList;
}
public class Role {
	private int id;
	private String rolename;
}

​ mapper文件

<resultMap id="userRoleMap" type="com.zmx.pojo.User">
        <result property="id" column="id"></result>
        <result property="username" column="username"></result>

        <collection property="roleList"  ofType="com.zmx.pojo.Role">
            <result property="id" column="rid"></result>
            <result property="roleName" column="roleName"></result>
            <result property="roleDesc" column="roleDesc"></result>
        </collection>

    </resultMap>
    <select id="findAllUserAndRole" resultMap="userRoleMap">
        select u.*,r.*,r.id rid from user u
            left join sys_user_role ur on  u.id=ur.userid
            inner join sys_role r on ur.roleid=r.id
    </select>


15-18. 注解开发

常用注解:

@Insert:实现新增
@Update:实现更新
@Delete:实现删除
@Select:实现查询
@Result:实现结果集封装
@Results:可以与@Result ⼀起使⽤,封装多个结果集
@One:实现⼀对⼀结果集封装
@Many:实现⼀对多结果集封装

在这里插入图片描述

1). 注解CRUD开发

​ 直接在xxxMapper.java文件里边的方法加上注解,注解写对应sql

	//添加用户
    @Insert("INSERT INTO USER(id,username) 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);

测试过程可以在测试类中加入@before注解进行对代码的提取,实现对代码的复用

2).注解一对一

创建实体类

public class Order {
     private int id;
     private Date ordertime;
     private double total;
     //代表当前订单从属于哪⼀个客户
     private User user;
}
public class User {
 
     private int id;
     private String username;
     private String password;
     private Date birthday;
     //代表当前⽤户具备哪些订单
     private List<Order> orderList;
}

直接写配置文件或直接在方法加注解实现一样的效果

mapper.xml

 <resultMap id="orderMap" type="com.zmx.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.zmx.pojo.User">
            <result property="id" column="uid"></result>
            <result property="username" column="username"></result>
        </association>

    </resultMap>

 //查询订单的同时查询所属用户
    @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.zmx.mapper.IUserMapper.findUserById"))
    })
    @Select("select * from orders")
    public List<Order> findOrderUser();

3). 注解一对多

​ 实体类

public class Order {
     private int id;
     private Date ordertime;
     private double total;
     //代表当前订单从属于哪⼀个客户
     private User user;
}
public class User {
     private int id;
     private String username;
     private String password;
     private Date birthday;
     //代表当前⽤户具备哪些订单
     private List<Order> orderList;
}

注解方式

 //查询所有用户信息  同时查询每个用户关联的订单信息
    @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.zmx.mapper.IOrderMapper.findOrderByUid"))

    })
    public List<User> findAll();

 	@Select("select * from orders where id = #{id}")
    public List<Order> findOrderByUid(Integer id);

4). 注解多对多

​ 实体类

public class User {
     private int id;
     private String username;
     private String password;
     private Date birthday;
     //代表当前⽤户具备哪些订单
     private List<Order> orderList;
     //代表当前⽤户具备哪些⻆⾊
     private List<Role> roleList;
}
public class Role {
     private int id;
     private String rolename;
 }


注解方式

  //查询所有用户 同时查询每个用户关联的角色信息
    @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.zmx.mapper.IRoleMapper.findRoleByUid"))
    })
    public List<User> findAllUserAndRole();

  @Select("select * from sys_role r,sys_user_role ur where r.id=ur.roleid and ur.userid = #{uid}")
    public List<Role> findRoleByUid(Integer uid);




理解:多对多 存在中间表将浪着关联起来,每一条数据都能够互相对应另一张表的多条数据

(例子:一个用户可以是多个角色;一个角色也可以有多个用户)


19-24. myBatis缓存

1). 缓存概念

2). 一级缓存

证明该缓存的存在:
        //第一次查询id为1的用户
        User userById = iUserMapper.findUserById(1);
        //更新用户
        User user = new User();
        user.setId(1);
        user.setUsername("tom");
        iUserMapper.updateUser(user);
        //第二次查询
        User userById1 = iUserMapper.findUserById(1);
        System.out.println(userById==userById1);

​ 当第二次查询操作之前并没有进行更新操作时,两次的查询结果一致;如果进行了更新操作,会清空SqlSession中的 ⼀ 级缓存,⽬的为了让缓存中存储的是最新的信息,避免脏读。可以手动进行缓存刷新(sqlSession.clearCache())。

在这里插入图片描述

一级缓存源码分析:

在这里插入图片描述

​ 查看clearCache()时,流程⾛到Perpetualcache中的clear()⽅法之后,会调⽤cache.clear()⽅法 ,cache就是private Map cache = new HashMap();所以说cache.clear()其实就是map.clear() 也就是说,缓存其实就是本地存放的⼀个map对象,每⼀个SqISession都会存放⼀个map对象的引⽤

Executor是 执⾏器,⽤来执⾏SQL请求,清除缓存的⽅法也在Executor,缓存的创建有可能在Executor  找到createCacheKey方法 

​ 缓存的创建:offset limit 分页参数

CacheKey cacheKey = new CacheKey();
//MappedStatement 的 id
// id就是Sql语句的所在位置包名+类名+ SQL名称
cacheKey.update(ms.getId());
// offset 就是 0
cacheKey.update(rowBounds.getOffset());
// limit 就是 Integer.MAXVALUE
cacheKey.update(rowBounds.getLimit());
//具体的SQL语句
cacheKey.update(boundSql.getSql());
//后⾯是update 了 sql中带的参数
cacheKey.update(value);
...
if (configuration.getEnvironment() != null) {
// issue #176
	cacheKey.update(configuration.getEnvironment().getId());
}

​ 创建缓存key会经过⼀系列的update⽅法,udate⽅法由⼀个CacheKey这个对象来执⾏的,这个update⽅法最终由updateList的list来把五个值存进去

在这里插入图片描述

configuration.getEnvironment().getId() 定义在mybatis-config.xml中的标签

​ ⼀级缓存更多是⽤于查询操作

@Override
  public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
    BoundSql boundSql = ms.getBoundSql(parameter);
    CacheKey key = createCacheKey(ms, parameter, rowBounds, boundSql);
    return query(ms, parameter, rowBounds, resultHandler, key, boundSql);
 }

  @SuppressWarnings("unchecked")
  @Override
  public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
    ErrorContext.instance().resource(ms.getResource()).activity("executing a query").object(ms.getId());
    if (closed) {
      throw new ExecutorException("Executor was closed.");
    }
    if (queryStack == 0 && ms.isFlushCacheRequired()) {
      clearLocalCache();
    }
    List<E> list;
    try {
      queryStack++;
      list = resultHandler == null ? (List<E>) localCache.getObject(key) : null;
      if (list != null) {
        handleLocallyCachedOutputParameters(ms, key, parameter, boundSql);
      } else {
        list = queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql);
      }
    } finally {
      queryStack--;
    }
    if (queryStack == 0) {
      for (DeferredLoad deferredLoad : deferredLoads) {
        deferredLoad.load();
      }
      // issue #601
      deferredLoads.clear();
      if (configuration.getLocalCacheScope() == LocalCacheScope.STATEMENT) {
        // issue #482
        clearLocalCache();
      }
    }
    return list;
  }

// queryFromDatabase ⽅法
 private <E> List<E> queryFromDatabase(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
    List<E> list;
    localCache.putObject(key, EXECUTION_PLACEHOLDER);
    try {
      list = doQuery(ms, parameter, rowBounds, resultHandler, boundSql);
    } finally {
      localCache.removeObject(key);
    }
    localCache.putObject(key, list);
    if (ms.getStatementType() == StatementType.CALLABLE) {
      localOutputParameterCache.putObject(key, parameter);
    }
    return list;
  }

查不到的话,就从数据库查,在queryFromDatabase中,会对localcache进⾏写⼊。 localcache
对象的put⽅法最终交给Map进⾏存放

private Map<Object, Object> cache = new HashMap<Object, Object>();
     @Override
     public void putObject(Object key, Object value) { cache.put(key, value);
}

3).二级缓存

​ ⼆级缓存的原理和⼀级缓存原理⼀样,第⼀次查询,会将数据放⼊缓存中,然后第⼆次查询则会直接去缓存中取。但是⼀级缓存是基于sqlSession的,⽽⼆级缓存是基于mapper⽂件的namespace的,也 就是说多sqlSession可以共享⼀个mapper中的⼆级缓存区域,并且如果两个mapper的namespace 相同,即使是两个mapper,那么这两个mapper中执⾏sql查询到的数据也将存在相同的⼆级缓存区域中

​ ⼆级缓存底层还是HashMap结构

需要配置进行使用

​ sqlMapConfig.xml⽂件中加⼊如下代码:

<!--开启⼆级缓存-->
<settings>
	 <setting name="cacheEnabled" value="true"/>
 </settings>
 其次在Mapper.xml⽂件中开启缓存
 <!--开启⼆级缓存-->
<cache></cache>


​ mapper.xml⽂件中就这么⼀个空标签,其实这⾥可以配置,PerpetualCache这个类是 mybatis默认实现缓存功能的类。不写type可以使⽤mybatis默认的缓存,也可以实现Cache接⼝来⾃定义缓存

@CacheNamespace //开启二级缓存
public interface IUserMapper {

}

​ 开启了⼆级缓存后,需要将要缓存的pojo实现Serializable接⼝,为了将缓存数据取出执⾏反序列化操
作,因为⼆级缓存数据存储介质多种多样,不⼀定只存在内存中,有可能存在硬盘中,如果我们要再取
这个缓存的话,就需要反序列化。所以mybatis中的pojo都去实现Serializable接⼝

​ 测试二级缓存

 @Test
    public void SecondLevelCache(){

        SqlSession sqlSession1 = sqlSessionFactory.openSession(true);
        SqlSession sqlSession2 = sqlSessionFactory.openSession(true);
        SqlSession sqlSession3 = sqlSessionFactory.openSession(true);

        IUserMapper mapper1 = sqlSession1.getMapper(IUserMapper.class);
        IUserMapper mapper2 = sqlSession2.getMapper(IUserMapper.class);
        IUserMapper mapper3 = sqlSession3.getMapper(IUserMapper.class);

        User userById = mapper1.findUserById(1);
        sqlSession1.close();//清空一级缓存

        User user = new User();
        user.setId(1);
        user.setUsername("33");
        mapper3.updateUser(user);
        sqlSession3.commit();
        User userById1 = mapper2.findUserById(1);
        System.out.println(userById == userById1);

    }

其他:

​ mybatis中还可以配置userCache和flushCache等配置项,userCache是⽤来设置是否禁⽤⼆级缓 存
的,在statement中设置useCache=false可以禁⽤当前select语句的⼆级缓存,即每次查询都会发出 sql
去查询,默认情况是true,即该sql使⽤⼆级缓存

<select id="selectUserByUserId" useCache="false"resultType="com.zmx.pojo.User" parameterType="int">
 	select * from user where id=#{id}
</select>

	//根据id查询用户
    @Options(useCache = false)
    @Select("select * from user where id=#{id}")
    public User findUserById(Integer id);

​ 这种情况是针对每次查询都需要最新的数据sql,要设置成useCache=false,禁⽤⼆级缓存,直接从数 据库中获取。
​ 在mapper的同⼀个namespace中,如果有其它insert、update, delete操作数据后需要刷新缓 存,如果 不执⾏刷新缓存会出现脏读。
​ 设置statement配置中的flushCache="true”属性,默认情况下为true,即刷新缓存,如果改成false则 不
会刷新。使⽤缓存时如果⼿动修改数据库表中的查询数据会出现脏读。

<select id="selectUserByUserId" flushCache="true" useCache="false"
			resultType="com.lagou.pojo.User" parameterType="int">
 	select * from user where id=#{id}
</select>

​ ⼀般下执⾏完commit操作都需要刷新缓存,flushCache=true表示刷新缓存,这样可以避免数据库脏
读。所以不⽤设置,默认即可

4). 二级缓存整合redis

​ 需要redis的原因:是这个缓存是单服务器⼯作,⽆法实现分布式缓存,需要找⼀个分布式的缓存,专⻔⽤来存储缓存数据的,这样不同的服务器要缓存数据都往它那⾥存,取缓存数据也从它那⾥取。

​ mybatis提供了⼀个eache接⼝,如果要实现⾃⼰的缓存逻辑,实现cache接⼝开发即可。mybatis本身默认实现了⼀个,但是这个缓存的实现⽆法实现分布式缓存,所以要⾃⼰实现。

​ redis分布式缓存就可以,mybatis提供了⼀个针对cache接⼝的redis实现类,该类存在mybatis-redis包中

使用步骤

​ 引入依赖

<dependency>
     <groupId>org.mybatis.caches</groupId>
     <artifactId>mybatis-redis</artifactId>
     <version>1.0.0-beta2</version>
</dependency>

​ 加入注解

@CacheNamespace(implementation = RedisCache.class) //开启二级缓存
public interface IUserMapper {}

启动redis  加入redis.properties
redis.host=localhost
redis.port=6379
redis.connectionTimeout=5000
redis.password=
redis.database=0

测试
源码分析

​ RedisCache和普遍实现Mybatis的缓存⽅案⼤同⼩异,实现Cache接⼝,并使⽤jedis操作缓存

​ RedisCache在mybatis启动的时候,由MyBatis的CacheBuilder创建,创建的⽅式就是调⽤RedisCache的带有String参数的构造⽅法,即RedisCache(String id);⽽在RedisCache的构造⽅法中,调⽤了 RedisConfigu rationBuilder 来创建 RedisConfig 对象,并使⽤ RedisConfig 来创建JedisPool。

​ RedisConfig类继承了 JedisPoolConfig,并提供了 host,port等属性的包装

public class RedisConfig extends JedisPoolConfig {
        private String host = Protocol.DEFAULT_HOST;
        private int port = Protocol.DEFAULT_PORT;
        private int connectionTimeout = Protocol.DEFAULT_TIMEOUT;
        private int soTimeout = Protocol.DEFAULT_TIMEOUT;
        private String password;
        private int database = Protocol.DEFAULT_DATABASE;
        private String clientName;
}

RedisConfig对象是由RedisConfigurationBuilder创建的

public RedisConfig parseConfiguration(ClassLoader classLoader) {
     Properties config = new Properties();
     InputStream input = classLoader.getResourceAsStream(redisPropertiesFilename);
     if (input != null) {
         try {
        	 config.load(input);
         } catch (IOException e) {
         throw new RuntimeException(  "An error occurred while reading classpath property '"
             + redisPropertiesFilename + "', see nested exceptions", e);
         } finally {
             try {
             input.close();
             } catch (IOException e) {
             // close quietly
             }
         }
    }
     RedisConfig jedisConfig = new RedisConfig();
     setConfigProperties(config, jedisConfig);
     return jedisConfig;
}	

​ 核⼼的⽅法就是parseConfiguration⽅法,该⽅法从classpath中读取⼀个redis.properties⽂件并将该配置⽂件中的内容设置到RedisConfig对象中,并返回;接下来,RedisCache使⽤ RedisConfig类创建完成edisPool;在RedisCache中实现了⼀个简单的模板⽅法,⽤来操作Redis

private Object execute(RedisCallback callback) {
     Jedis jedis = pool.getResource();
     try {
    	 return callback.doWithRedis(jedis);
     } finally {
    	 jedis.close();
     }
}

模板接⼝为RedisCallback,这个接⼝中就只需要实现了⼀个doWithRedis⽅法

public interface RedisCallback {
 	Object doWithRedis(Jedis jedis);
}

Cache中最重要的两个⽅法:putObject和getObject,通过这两个⽅法来查看mybatis-redis
储存数据的格式

@Override
public void putObject(final Object key, final Object value) {	
 execute(new RedisCallback() {
     @Override
     public Object doWithRedis(Jedis jedis) {
        jedis.hset(id.toString().getBytes(), key.toString().getBytes(), 																		SerializeUtil.serialize(value));
         return null;
         }
         });
    }
     @Override
     public Object getObject(final Object key) {
        return this.execute(new RedisCallback() {
            public Object doWithRedis(Jedis jedis) {
                return SerializeUtil.unserialize(jedis.hget(RedisCache.this.id.toString().getBytes(), 								key.toString().getBytes()));
            }
        });
    }

     @Override
     public Object doWithRedis(Jedis jedis) {
     return SerializeUtil.unserialize(jedis.hget(id.toString().getBytes(),
    		key.toString().getBytes()));
     }
     });
}

​ mybatis-redis在存储数据的时候,是使⽤的hash结构,把cache的id作为这个hash的key (cache的id在mybatis中就是mapper的namespace);这个mapper中的查询缓存数据作为 hash的field,需要缓存的内容直接使⽤SerializeUtil存储,SerializeUtil和其他的序列化类差不多,负责对象的序列化和反序列化


25.mybatis插件

​ 四⼤组件 (Executor、StatementHandler、ParameterHandler、ResultSetHandler)处提供了插件扩展机制。Mybatis对持久层的操作就是借助于四⼤核⼼对象。MyBatis⽀持⽤插件对四⼤核⼼对象进⾏拦截对mybatis来说插件就是拦截器,⽤来增强核⼼对象的功能,增强功能本质上是借助于底层的动态代理实现的,换句话说,MyBatis中的四⼤对象都是代理对象。

MyBatis所允许拦截的⽅法如下:
执⾏器Executor (update、query、commit、rollback等⽅法);
SQL语法构建器StatementHandler (prepare、parameterize、batch、updates query等⽅ 法);
参数处理器ParameterHandler (getParameterObject、setParameters⽅法);
结果集处理器ResultSetHandler (handleResultSets、handleOutputParameters等⽅法);

插件原理:

在四⼤对象创建的时候
1、每个创建出来的对象不是直接返回的,⽽是interceptorChain.pluginAll(parameterHandler);
2、获取到所有的Interceptor (拦截器)(插件需要实现的接⼝);调⽤ interceptor.plugin(target);返回 target 包装后的对象
3、插件机制,我们可以使⽤插件为⽬标对象创建⼀个代理对象;AOP (⾯向切⾯)我们的插件可 以为四⼤对象创建出代理对象,代理对象就可以拦截到四⼤对象的每⼀个执⾏;

​ interceptorChain保存了所有的拦截器(interceptors),是mybatis初始化的时候创建的。调⽤拦截器链中的拦截器依次的对⽬标进⾏拦截或增强。interceptor.plugin(target)中的target就可以理解为mybatis中的四⼤对象。返回的target是被重重代理后的对象

例子:拦截Executor的query⽅法

@Intercepts({
 @Signature(
 	type = Executor.class,
 	method = "query",
 	args= {MappedStatement.class,Object.class,RowBounds.class,ResultHandler.class}
 )
}) 
public class ExeunplePlugin implements Interceptor {
 /
     /省略逻辑
}

​ 将插件配置到sqlMapConfig.xml

<plugins>
	 <plugin interceptor="com.lagou.plugin.ExamplePlugin">
 	</plugin>
</plugins>

​ 这样MyBatis在启动时可以加载插件,并保存插件实例到相关对象(InterceptorChain,拦截器链) 中。待准备⼯作做完后,MyBatis处于就绪状态。我们在执⾏SQL时,需要先通过DefaultSqlSessionFactory创建 SqlSession。Executor 实例会在创建 SqlSession 的过程中被创建, Executor实例创建完毕后,MyBatis会通过JDK动态代理为实例⽣成代理类。这样,插件逻辑即可在 Executor相关⽅法被调⽤前执⾏。

自定义mybatis插件:

@Intercepts({
        @Signature(type = StatementHandler.class,
                    method = "prepare",
                    args = {Connection.class,Integer.class})
})
public class MyPlugin implements Interceptor {

    //拦截方法: 只要被拦截的目标对象的目标方法被执行时 每次都会执行intercept方法
    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        System.out.println("对方法进行了增强");
        return invocation.proceed();//原方法执行
    }

    //为了把当前的拦截器生成代理存到拦截器链中
    @Override
    public Object plugin(Object target) {
        Object wrap = Plugin.wrap(target, this);
        return wrap;
    }

    //获取配置文件参数
    @Override
    public void setProperties(Properties properties) {
        System.out.println("获取到的配置文件参数是"+properties);
    }
}

<plugins>
	 <plugin interceptor="com.lagou.plugin.MySqlPagingPlugin">
	 <!--配置参数-->
	 <property name="name" value="Bob"/>
	 </plugin>
</plugins>

plugin源码分析:

​ Plugin实现了 InvocationHandler接⼝,因此它的invoke⽅法会拦截所有的⽅法调⽤。invoke⽅法会 对
所拦截的⽅法进⾏检测,以决定是否执⾏插件逻辑

public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
         try {
             /*
             *获取被拦截⽅法列表,⽐如:
             * signatureMap.get(Executor.class), 可能返回 [query, update, commit]
             */
             Set<Method> methods = signatureMap.get(method.getDeclaringClass());
             //检测⽅法列表是否包含被拦截的⽅法
             if (methods != null && methods.contains(method)) {
             	//执⾏插件逻辑
             	return interceptor.intercept(new Invocation(target, method,  args));
             }
             //执⾏被拦截的⽅法
             return method.invoke(target, args);
         } catch(Exception e){
                 
         }
 }

​ ⾸先,invoke⽅法会检测被拦截⽅法是否配置在插件的@Signature注解中,若是,则执⾏插件逻辑,否则执⾏被拦截⽅法。插件逻辑封装在intercept中,该⽅法的参数类型为Invocationo Invocation主要⽤于存储⽬标类,⽅法以及⽅法参数列表

第三方插件:

​ 1.pagehelper

​ 开发步骤:
​ ① 导⼊通⽤PageHelper的坐标

<dependency>
     <groupId>com.github.pagehelper</groupId>
     <artifactId>pagehelper</artifactId>
     <version>3.7.5</version>
 </dependency>
 <dependency>
     <groupId>com.github.jsqlparser</groupId>
     <artifactId>jsqlparser</artifactId>
     <version>0.9.1</version>
 </dependency>	

​ ② 在mybatis核⼼配置⽂件中配置PageHelper插件

<!--注意:分⻚助⼿的插件 配置在通⽤馆mapper之前*-->*
 <plugin interceptor="com.github.pagehelper.PageHelper">
 	<!—指定⽅⾔ >
	 <property name="dialect" value="mysql"/>
 </plugin>

​ ③ 测试分⻚数据获取

  @Test
    public void pageHelperTest() throws IOException {
        PageHelper.startPage(1, 1);
        List<User> all = iUserMapper.selectUser();
        for (User user : all) {
            System.out.println(user);
        }
        PageInfo<User> userPageInfo = new PageInfo<>(all);
        System.out.println("总条数"+userPageInfo.getTotal());
        System.out.println("总页数"+userPageInfo.getPages());
        System.out.println("当前页"+userPageInfo.getPageNum());
        System.out.println("每页显示条数"+userPageInfo.getPageSize());
    }

​ 2.通用mapper

​ ① 导⼊通用mappe的坐标

<dependency>
     <groupId>tk.mybatis</groupId>
     <artifactId>mapper</artifactId>
     <version>3.1.2</version>
</dependency>

​ ② 在mybatis核⼼配置⽂件中配置PageHelper插件

 <plugin interceptor="tk.mybatis.mapper.mapperhelper.MapperInterceptor">
         <property name="mappers" value="tk.mybatis.mapper.common.Mapper"/>
</plugin>

​ ③ 实体类

@Table(name = "user")
public class User implements Serializable {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY) //设置主键的生成策略
    private Integer id;

    private String username;

    @Override
    public String toString() {
        return "User{" +
                "id=" + id +
                ", username='" + username + '\'' +
                '}';
    }
}

public interface UserMapper extends Mapper<User> {


}


	 @Test
    public void mapperTest() throws IOException {
        InputStream resourceAsStream = Resources.getResourceAsStream("sqlMapConfig.xml");
        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(resourceAsStream);
        SqlSession sqlSession = sqlSessionFactory.openSession();
        UserMapper mapper = sqlSession.getMapper(UserMapper.class);
        User user = new User();
        user.setId(1);
        User user1 = mapper.selectOne(user);
        System.out.println(user1);

    }

注意:

public class UserTest {
 @Test
 public void test1() throws IOException {
     Inputstream resourceAsStream =   Resources.getResourceAsStream("sqlMapConfig.xml");
     SqlSessionFactory build = new    SqlSessionFactoryBuilder().build(resourceAsStream);
     SqlSession sqlSession = build.openSession();
     UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
     User user = new User();
     user.setId(4);
     //(1)mapper基础接⼝
     //select 接⼝
     User user1 = userMapper.selectOne(user); //根据实体中的属性进⾏查询,只能有
    —个返回值
     List<User> users = userMapper.select(null); //查询全部结果
     userMapper.selectByPrimaryKey(1); //根据主键字段进⾏查询,⽅法参数必须包含完
    整的主键属性,查询条件使⽤等号
     userMapper.selectCount(user); //根据实体中的属性查询总数,查询条件使⽤等号
     // insert 接⼝
     int insert = userMapper.insert(user); //保存⼀个实体,null值也会保存,不会使
    ⽤数据库默认值
     int i = userMapper.insertSelective(user); //保存实体,null的属性不会保存,
    会使⽤数据库默认值
     // update 接⼝
     int i1 = userMapper.updateByPrimaryKey(user);//根据主键更新实体全部字段,
    null值会被更新
     // delete 接⼝
     int delete = userMapper.delete(user); //根据实体属性作为条件进⾏删除,查询条
    件 使⽤等号
     userMapper.deleteByPrimaryKey(1); //根据主键字段进⾏删除,⽅法参数必须包含完
    整的主键属性
     //(2)example⽅法
     Example example = new Example(User.class);
     example.createCriteria().andEqualTo("id", 1);
     example.createCriteria().andLike("val", "1");
     //⾃定义查询
     List<User> users1 = userMapper.selectByExample(example);
     }
}

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值