Mybatis

Mybatis总结

概述

MyBatis是一个优秀的持久层框架,它对jdbc的操作数据库的过程进行封装,使开发者只需要关注 SQL 本身,而不需要花费精力去处理例如加载驱动、建立连接、创建sql语句执行对象如statement、手动设置参数、获取结果集等jdbc繁杂的过程代码。简言之。就是简化数据库增删改查代码。

使用Java操作数据库的话,JDK给我们提供了一层对各个数据库的封装,也就是JDBC,它屏蔽了数据库之间的差异,使用JDBC可以统一操作。

mybatis安装配置

在工程目录下的pom.xml中配置

<dependency>
      <groupId>org.mybatis</groupId>
      <artifactId>mybatis</artifactId>
      <version>3.5.0</version>
</dependency>

Mybatis核心配置文件(mybatis-config.xml)

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
        PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
    <!-- 引入外部properties文件 -->
    <properties resource="jdbc.properties" />
    
    <!-- 定义类型别名,在xxxMapper.xml文件中就可以用别名代替很长的类名 -->
    <!--<typeAliases>
        <typeAlias type="com.lanou.spring.bean.User" alias="User" />
    </typeAliases>-->
    
    <settings>
        <setting name="mapUnderscoreToCamelCase" value="true"/>
    </settings>

    <!-- 配置不同环境的参数 -->
     <!-- 定义数据库信息,默认使用id是development数据库构建环境 -->
    <environments default="development">
        <!-- 开发环境数据库、事务配置 -->
        <environment id="development">
            <!-- 事务管理使用JDBC的事务 -->
            <transactionManager type="JDBC"/>


            <!-- 配置开发环境数据源 -->
            <dataSource type="POOLED">
                <property name="driver" value="${jdbc.driver}"/>
                <property name="url" value="${jdbc.url}"/>
                <property name="username" value="${jdbc.user}"/>
                <property name="password" value="${jdbc.password}"/>
            </dataSource>
        </environment>
    </environments>

    <!-- 将mapper SQL映射文件包含进来 -->
    <mappers>
        <mapper resource="mappers/userMapper.xml"/>
    </mappers>
</configuration>

sqlSessionFactory

一个该对象对应一个数据源。

SqlSessionFactory是一个工厂接口,而不是一个实现类。它的任务就是创建SqlSession。SqlSession类似于一个JDBC的Connection对象。

从XML中构建,如上的核心配置文件

SqlSession

一个sqlsession 对象代表一次到数据库的会话

功能:

用于负责执行sql语句的对象

操作事务:提交,回滚

非线程安全,不能作为一个类的静态变量,最好是用完后

在finally中关掉它

mapper

用于映射数据库CRUD操作,将SQL语句和Java接口进行绑定

基于xml方式映射SQL

基于注解方式映射SQL

通过SqlSession对象调用相应的mapper接口完成对数据库的操作

核心配置文件中的settings

  • mapUnderscoreToCamelCase

是否开启自动驼峰命名规则(camel case)映射,即从经典数据库列名
A_COLUMN 到经典 Java 属性名 aColumn 的类似映射。

如private Timestamp lastlOgintime;

字段last_login_time ,下划线参与,但字母不分大小写

<settings>
        <setting name="mapUnderscoreToCamelCase" value="true"/>
</settings>
  • useGeneratedKeys

    允许 JDBC 支持自动生成主键,需要驱动支持。
    如果设置为 true 则这个设置强制使用自动生成主键

    <!-- 给insert语句添加useGeneratedKeys、keyProperty后,mybatis会将自增的id值直接赋值到传进来的user对象的id属性上
            useGeneratedKeys: 指定需要获取数据库自增的id
            keyProperty: 指定自增字段的名称
         -->
        <!--<insert id="insertUser" useGeneratedKeys="true" keyProperty="id">
      insert into user(nick_name,account,password,last_login_time)  字段名
      values (#{nickName},#{account},#{password},#{lastLoginTime})-->  #{属性名}
    

结果集映射

ResultType方式

ResultType方式适用于数据库结果集可以直接映射成一个Java类的情况

实体类

@Getter
@Setter
@ToString
public class User {
    private Integer id;
    private String nickName;
    private String account;
    private String password;
    private Timestamp lastLoginTime;
    //private Timestamp lastlOgintime;
}

使用方法

<select id="selectAllUser" resultType="user">
    select * from user
</select>
ResultMap方式

ResultMap方式适用于复杂的结果集映射,比如数据库返回的结果集中的列名和JavaBean无法一一对应,或者对象间存在一对一、一对多关联映射时。

解决数据库列名与Java类中属性名不一致的映射问题

上述的settings无法满足要求

如private Timestamp lastloginttime;

<resultMap id="userMap" type="user">
    <id property="id" column="id" />
    <result property="nickName" column="nick_name"/>
    <result property="account" column="account"/>
    <result property="password" column="password"/>
    <result property="lastloginttime" column="last_login_time"/>
  </resultMap>

  <!--<select id="selectAllUser" resultType="user">
    select * from user
</select>-->

  <select id="selectAllUser" resultMap="userMap">
    select * from user
  </select>
解决一对一及一对多映射查询问题
示例:

​ xxxMapper.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">
<!-- namespace 对应接口的全名 -- >
<mapper namespace="com.lanou.spring.mapper.xxxMapper">
</mapper>

mybatis-config.xml

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
        PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
<!-- 引入外部properties文件,路径从类路径的根目录开始 -->
    <properties resource="jdbc.properties" />

    <typeAliases>
        <!--单个类配置别名-->
       <!-- <typeAlias type="com.lanou.spring.bean.King" alias="King" />-->
        <!--统一配置某个包下的所有类的别名-->
        <package name="com.lanou.spring.bean"/>
    </typeAliases>

    <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.user}"/>
                <property name="password" value="${jdbc.password}"/>
            </dataSource>
        </environment>
    </environments>
    <mappers>
        <mapper resource="kingMapper.xml"/>
    </mappers>
</configuration>
解决一对一映射查询问题

实体类:

@Getter
@Setter
@ToString
public class King {
    private Integer id;
    private String name;
    private Queen queen;


}

@Setter
@Getter
@ToString
public class Queen {
    private Integer id;
    private String name;
    private Integer kId;

}

包 com.lanou.spring.mapper 里放接口,内部有数据库的增删改查方法

public interface KingMapper {
    King findKingByKingId(Integer id);
}

jdbc.properties

jdbc.url=jdbc:mysql://localhost:3306/lanou?characterEncoding=utf8&zeroDateTimeBehavior=convertToNull
jdbc.driver=com.mysql.jdbc.Driver
jdbc.user=root
jdbc.password=123456

kingMapper.xml

<mapper namespace="com.lanou.spring.mapper.KingMapper">
    <!--映射结果集 -->
<resultMap id="kingMap" type="king" >
         <id property="id" column="kid"></id>
         <result property="name" column="kname" />
         <association property="queen" javaType="queen"  resultMap="queenMap"/>

     </resultMap>

     <resultMap id="queenMap" type="queen">
         <id property="id" column="qid"/>
         <result property="name" column="qname"/>
         <result column="qkid" property="kId" />
     </resultMap>
    <!--id 对应接口中的方法名,引用定义的resultmap,将查询的结果集映射到kingMap上 -->
     <select id="findKingByKingId" resultMap="kingMap">
         select k.id kid, k.name kname,q.id qid,q.name qname,q.k_id qkid
             from king k
             LEFT JOIN queen q
             on q.k_id = k.id
             where k.id = #{id}
             ;
     </select>
    </mapper>
解决一对一映射查询问题

实体类

@Getter
@Setter
@ToString
public class King1 {
    private Integer id;
    private String name;
    private List<Girl> girls;
}

@Setter
@Getter
@ToString
public class Girl {
    private Integer id;
    private String name;
    private Integer kId;
}

接口

public interface KingMapper {
    King1 findKingByKingId(Integer id);
}

kingMapper.xml

<resultMap id="kingMap" type="king1" >
        <id property="id" column="kid"></id>
        <result property="name" column="kname" />
        <collection property="girls" ofType="girl" resultMap="girlMap"/>
    </resultMap>

    <resultMap id="girlMap" type="girl">
        <id property="id" column="gid"/>
        <id property="name" column="gname"/>
       <id property="kId" column="gkid" />

    </resultMap>
    <select id="findKingByKingId" resultMap="kingMap">
        select k.id kid, k.name kname,g.id gid,g.name gname,g.k_id gkid
            from king k
            LEFT JOIN girl g
            on g.k_id = k.id
            where k.id = #{id}
            ;

    </select>

测试类

public class TestMain {

public static void main(String[] args) throws IOException {
        InputStream resource = Resources.getResourceAsStream("mybatis-config.xml");
        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(resource
        );
        SqlSession sqlSession = sqlSessionFactory.openSession(true);
        KingMapper mapper = sqlSession.getMapper(KingMapper.class);
       /* King king = mapper.findKingByKingId(32);
        System.out.println(king);*/
        King1 king1 = mapper.findKingByKingId(31);
        System.out.println(king1);
    }
}

批量插入
通过forEach动态SQL方式

原理是直接通过foreach动态标签,根据传过来的参数数量动态生成一个很长的sql语句,一个语句就是一次批量插入

接口

public interface UserMapper1 {
   
    int batchInsert(List<User1> user1List);
    
}

UserMapper.xml

<insert id="batchInsert">
        insert into user(account,status,createtime)
        values
        <foreach collection="list"  item="user"  close=";" separator=",">
            (#{user.account},#{user.status},#{user.createTime})
        </foreach>

    </insert>

测试类

 public static void main(String[] args) throws IOException {
        InputStream resource = Resources.getResourceAsStream("mybatis-config.xml");
        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(resource
        );
        SqlSession sqlSession = sqlSessionFactory.openSession(true);
      
        UserMapper1 mapper = sqlSession.getMapper(UserMapper1.class);
        testBatchInsertexecutor(sqlSession, mapper);

    }

    public static void testBatchInsert(UserMapper1 mapper){
        List<User1> user1List = new ArrayList<>();
        user1List.add(new User1("谁1",1,new Timestamp(System.currentTimeMillis())));
        user1List.add(new User1("谁2",1,new Timestamp(System.currentTimeMillis())));
        user1List.add(new User1("谁3",1,new Timestamp(System.currentTimeMillis())));

        int rows = mapper.batchInsert(user1List);
        System.out.println(rows);


    }
通过Executor.BATCH的方式

实现原理:在底层的mapper接口和mapper映射文件中,都只是一个普通插入单条数据的写法,它通过在上层获取sqlSession时,指定执行类型是批量ExecutorType.BATCH方式,实现每次执行完单条数据以后并没有真正写入数据库,只有当调用sqlSession.flushStatements(),才会将数据一次写入数据库。

接口

public interface UserMapper1 {
   
    int batchInsertExecutor(User1 user);
}

UserMapper.xml

 <insert id="batchInsertExecutor">
        insert into user(account,status,createtime)
        values
            (#{account},#{status},#{createTime})
    </insert>

测试类

 public static void testBatchInsertexecutor(SqlSession sqlSession, UserMapper1 mapper){
        List<User1> user1List = new ArrayList<>();
        for(int i = 1; i < 30; i++){
            user1List.add(new User1("芒种1",1,new Timestamp(System.currentTimeMillis())));
            user1List.add(new User1("芒种2",1,new Timestamp(System.currentTimeMillis())));
            user1List.add(new User1("芒种3",1,new Timestamp(System.currentTimeMillis())));
        }
        int batchSize = 8; //设置批量添加条数,等到flushStatements()执行完,即写到数据库里
        int count = 0 ;
        List<BatchResult> resultList = new ArrayList<>);

        for(User1 user: user1List){
          mapper.batchInsertExecutor(user);
          count++;
          if(count % batchSize == 0){
              resultList.addAll(sqlSession.flushStatements());
          }

        }
        if(count % batchSize != 0 ){
            resultList.addAll(sqlSession.flushStatements());
        }

        int rows = 0;
        for(BatchResult batchResult: resultList){
            int[] updateCounts = batchResult.getUpdateCounts();  //某一批影响的条数
                rows += updateCount;
            }
        }
        System.out.println("批量插入成功,响应的行数:" + rows);
 }


缓存
一级缓存(本地缓存)

一级缓存的作用域默认是一个SqlSession。Mybatis默认开启一级缓存。

即在同一个SqlSession中,执行相同的查询SQL,第一次会去数据库进行查询,并写到缓存中;
第二次以后是直接去缓存中取。
当执行SQL查询中间发生了增删改的操作,MyBatis会把SqlSession的缓存清空。

/**
 * 一级缓存(本地缓存)默认已经启用了本地的会话缓存
 */

public class TestMain {
    public static void main(String[] args) throws IOException {
        InputStream in = Resources.getResourceAsStream("mybatis-config.xml");
        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(in);
        SqlSession sqlSession = sqlSessionFactory.openSession(true);
        testLocalCache(sqlSession );
    }


    public static void testLocalCache(SqlSession sqlSession ){

        //同一个sqlSession执行同样的sql语句,会命中缓存

        StuMapper mapper = sqlSession.getMapper(StuMapper.class);
        for(int i = 0; i < 5; i++){
            List<Stu> stuList = mapper.selectAll();
            for(Stu stu: stuList){
                System.out.println("第" + i + "次");
                System.out.println(stu);
            }
        }
        //更新操作会使一级缓存失效,下次自动从数据库查询最新数据
        Stu stu = new Stu();
        stu.setSid(120);
        stu.setSmajor("网工");
        mapper.updateStu(stu);


        List<Stu> stuList1 = mapper.selectAll();
        for(Stu stu1: stuList){
            System.out.println(stu1);
        }

    }

}
禁用一级缓存
  • 在映射文件StuMapper.xml中给对应的select标签上添加flushCache=“true”属性

    <select id="selectAll" resultMap="BaseResultMap" flushCache="true">
        select sid, sname, ssex, sage, smajor, sphone, isDelete
        from stu
     </select>
    
  • 在核心配置文件mybatis-config.xml中将localCacheScope设置成STATEMENT(默认值是SESSION)

 <settings>
        <!-- 开启将数据库中下划线连接的字段自动映射为Java的小驼峰命名 -->
        <setting name="mapUnderscoreToCamelCase" value="true"/>
        <setting name="localCacheScope" value="STATEMENT"/>
    </settings>
二级缓存(全局缓存)
什么是二级缓存?

Mybatis中二级缓存相比一级缓存来说是一个作用域更大的缓存方案。 二级缓存的作用域可以跨多个SqlSession,只要是同一个namespace下的mapper映射文件都可以共享缓存。但是不能跨SqlSessionFactory。

Mybatis二级缓存需要手动开启。

1打开全局二级缓存开关

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

在具体的Mapper中开启二级缓存

<cache/> 
<!-- <cache>标签表示当前这个 mapper 映射将使用二级缓存,能否命中二级缓存就看多次查询是否属于同一个namespace。-- >

2、创建多个SqlSession对象去执行同样的查询语句

public static void testSecondCache(SqlSessionFactory sqlSessionFactory) {


        for(int i = 0; i < 5; i++){
            SqlSession sqlSession = sqlSessionFactory.openSession(true);
            StuMapper mapper = sqlSession.getMapper(StuMapper.class);
            List<Stu> stuList = mapper.selectAll();
            for(Stu stu: stuList){
                System.out.println("第" + i + "次");
                System.out.println(stu);
            }
            sqlSession.close();
        }
}

3、观察日志,查看真正执行了多少次查询
4、使用任意一个SqlSession执行一次更新操作,然后再用其他SqlSession执行查询,观察查询是否命中缓存,结果是否是更新后的最新结果

更新操作后,命中率降低后,重新查数据库。只要是发生了增删改的操作,触发缓存更新

注意: 由于二级缓存是事务性的,只有提交了事务才会写入缓存中。所以测试的时候不要忘记提交事务(或者直接close SqlSession也行)

二级缓存注意事项

当我们在使用二级缓存时,所缓存的类一定要实现 java.io.Serializable 接口,这种就可以使用序列化 方式来保存对象。

public class Stu implements Serializable { }
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值