MyBatis框架(常用基本功能)

What is Mybatis?

mybatis是支持普通SQl查询,存储过程和高级映射的优秀持久层框架,半自动ORM框架。

  • 消除了几乎所有JDBC代码和参数的手工设置以及搜索结果集的检索;
  • 使用简单的xml或注解用于配置和原始映射,将接口和Java的对象映射成数据库中的记录。

本质
mybatis的本质就是解决Java和mysql之间的协调作用,Java通过面向对象的方式去操作mysql。

在这里插入图片描述

框架使用步骤

添加依赖

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

添加配置文件 mybatis-config.xml

这个配置文件中包含了MyBatis系统的核心设置;

  • environments:事务管理和连接池的配置
  • TransactionManager:事务作用域和控制方式的事务管理器
  • DataSource:获取数据库连接实例的数据源
  • mappers:包含一组mapper映射器

这些mapper的XML文件包含了SQL代码和映射定义信息;

用来连接数据库
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>
    <environments default="development">
        <environment id="development">
            <transactionManager type="JDBC"/>
            <!--配置了mybatis连接数据库的信息-->
            <dataSource type="POOLED">
                <property name="driver" value="com.mysql.jdbc.Driver"/>
                <property name="url" value="jdbc:mysql://localhost:3306/test"/>
                <property name="username" value="root"/>
                <property name="password" value="westos"/>
            </dataSource>
        </environment>
    </environments>
    <mappers>
        <!--指定映射文件的位置-->
        <mapper resource="mapper/StudentMapper.xml"/>
    </mappers>
</configuration>

提供一个 映射文件
用来管理sql语句,描述了sql语句跟数据库之间的映射关系;

mapper/StudentMapper.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(命名空间) 用来防止sql的命名冲突的-->
<mapper namespace="mapper.StudentMapper">
    <!--#{} 用来获取student参数对象中的sname属性-->
    <!--parameterType用来指定获取参数的类型,
    因为student属性参数类型不一致,所以直接使用student类型-->
    
<!--useGeneratedKeys告诉mybatis要使用数据库产生的主键值,默认是不使用;
        keyProperty逐渐对应的属性名-->
        
    <insert id="abc" parameterType="domain.Student" 
    useGeneratedKeys="true" keyProperty="sid">
        insert into student(sid,sname,birthday,sex)
        values (null ,#{sname},#{birthday},#{sex})
    </insert>

	    <delete id="delete" parameterType="int">
        delete from student where sid= #{sid}
    </delete>

    <!--这里的列名要和student类的属性名一致-->
    <select id="select" resultType="domain.Student">
        select sid,sname,birthday,sex from student
    </select>

    <select id="selectById" resultType="domain.Student" parameterType="int">
        select * from student where sid=#{sid}
    </select>

    <select id="update" parameterType="domain.Student">
        update student
        <set>
            <if test="sname!=null">
                sname=#{sname},
            </if>
            <if test="birthday!=null">
                birthday=#{birthday},
            </if>
            <if test="sex!=null">
                sex=#{sex}
            </if>
        </set>
        where sid=#{sid}
    </select>

    <!-- m , n
   java.util.Map -> map
   java.util.List -> list
   java.lang.String -> string
   map.put("m", 0);
   map.put("n", 5);
   -->
    <select id="findByPage" parameterType="map" resultType="domain.Student">
        select * from student limit #{m},#{n}
    </select>
</mapper>

调用mybatis api使用映射文件真正执行增删改查

每一个MyBatis的应用都是以一个SqlSessionFactory的实例为中心的,SqlSessionFactory的实例可以通过SqlSessionFactoryBuilder获得,而SqlSessionFactoryBuilder则可以从XML配置文件或一个预先定制的Configuration的实力构建出SqlSessionFactory的实例;

  • SqlSessionFactoryBuilder:一旦创建了SqlSessionFactory就不再需要它了;最佳作用域是应用程序作用域;
  • SqlSessionFactory 用来创建SqlSession的工厂类,应该避免多次创建,最好使用单例模式或静态单例模式创建;最佳作用域是应用程序作用域;
  • SqlSession:每个线程都应该有它自己的SqlSession实例,实例不是线程安全的,最佳作用域是请求或方法作用域。
public class TestStudentMapper {
    static SqlSessionFactory factory;

    static {
        try {
            //读取配置文件
            InputStream in = Resources.getResourceAsStream("mybatis-config.xml");
            //创建sqlSession工厂类
            factory = new SqlSessionFactoryBuilder().build(in);
        } catch (IOException e) {
            e.printStackTrace();
        }
        


    }

    @Test
    //读取配置文件
    public void testInsert() throws IOException {

        //创建sqlSession ,这里的session更类似于jdbc中的Connection
        SqlSession sqlSession = factory.openSession();

        //执行新增
        Student stu = new Student();
        stu.setSname("小林");
        stu.setBirthday(new Date());
        stu.setSex("女");
        //参数一:namespace+sql_id  参数二:传递sql语句需要的java对象
        sqlSession.insert("mapper.StudentMapper.abc",stu);

        //执行增删改 没有启用自动提交事务
        sqlSession.commit();

        //关闭资源
        sqlSession.close();
    }

    @Test
    public void testDelete(){
        SqlSession sqlSession = factory.openSession();
        sqlSession.delete("mapper.StudentMapper.delete",1009);
        sqlSession.commit();
        sqlSession.close();
    }
    @Test
    public void testFindAll(){
        SqlSession sqlSession = factory.openSession();
        List<Student> stu = sqlSession.selectList("mapper.StudentMapper.select");
        sqlSession.close();
        for (Student student : stu) {
            System.out.println(student);
        }
    }

    @Test
    public void testFindById(){
        SqlSession sqlSession = factory.openSession();
        Student stu = sqlSession.selectOne("mapper.StudentMapper.selectById",1002);
        sqlSession.close();

        System.out.println(stu);

    }

    @Test
    public void testUpdate(){
        SqlSession sqlSession = factory.openSession();
        Student student = new Student();
        student.setSid(1008);
        student.setSex("女");
        sqlSession.update("mapper.StudentMapper.update",student);
        sqlSession.commit();
        sqlSession.close();
    }

    @Test
    public void findByPage(){
        SqlSession sqlSession = factory.openSession();
        HashMap<String, Integer> map = new HashMap<>();
        map.put("m",0);
        map.put("n",6);
        List<Student> list = sqlSession.selectList("mapper.StudentMapper.findByPage",map);
        sqlSession.close();
        for (Student student : list) {
            System.out.println(student);
        }

    }
}


在这里插入图片描述

通过日志工具监控Mybatis生成的sql语句

logback
添加依赖

  <dependency>
        <groupId>ch.qos.logback</groupId>
        <artifactId>logback-classic</artifactId>
        <version>1.2.3</version>
    </dependency>

添加一个logback.xml到resources文件夹;

<?xml version="1.0" encoding="UTF-8"?>
<configuration
        xmlns="http://ch.qos.logback/xml/ns/logback"
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xsi:schemaLocation="http://ch.qos.logback/xml/ns/logback logback.xsd">
    <!-- 输出控制,格式控制-->
    <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
        <encoder>
            <pattern>%date{yyyy-MM-dd HH:mm:ss} [%-5level] %logger{32} - %m%n </pattern>
        </encoder>
    </appender>
    <appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <!-- 日志文件名称 -->
        <file>logFile.log</file>
        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
            <!-- 每天产生一个新的日志文件 -->
            <fileNamePattern>logFile.%d{yyyy-MM-dd}.log</fileNamePattern>
            <!-- 保留 15 天的日志 -->
            <maxHistory>15</maxHistory>
        </rollingPolicy>
        <encoder>
            <pattern>%date{yyyy-MM-dd HH:mm:ss} [%-5level] %logger{17} - %m%n </pattern>
        </encoder>
    </appender>

    <!-- 用来控制查看那个类的日志内容(对mybatis name 代表命名空间) -->
    <logger name="mapper.StudentMapper" level="DEBUG" additivity="false">
        <!--输出到控制台-->
        <appender-ref ref="STDOUT"/>
        <!--输入到文件-->
        <appender-ref ref="FILE"/>
    </logger>

    <root level="ERROR">
        <appender-ref ref="STDOUT"/>
    </root>
</configuration>

输出日志信息
在这里插入图片描述

动态sql生成

  1. foreach 循环
    循环执行sql语句
    案例1:给定多个id同时删除
    <!--list是集合,x相当于临时变量-->
    <!--open:sql语句以什么开头
        close:sql语句以什么关闭
        separator:语句中间分隔符-->
    <delete id="deleteByIds" parameterType="list">
        delete from student where sid in
        <foreach collection="list" item="x" open="(" close=")" separator=",">
          #{x}
        </foreach>
    </delete>
  1. if where 生成动态标签;

案例2:给定多个条件进行查询
用来抵消多余and带来的影响;

    <select id="findByCondition" parameterType="map" resultType="domain.Student">
        select sid,sname,birthday,sex from student
        where 1=1
        <if test="sname!=null">
            and sname=#{sname}
        </if>
        <if test="birthday!=null">
            and birthday=#{birthday}
        </if>
        <if test="sex!=null">
            and sex=#{sex}
        </if>
    </select>

where标签去除多余的and;

    <select id="findByCondition" parameterType="map" resultType="domain.Student">
        select sid,sname,birthday,sex from student
        <where>
        <if test="sname!=null">
            and sname=#{sname}
        </if>
        <if test="birthday!=null">
            and birthday=#{birthday}
        </if>
        <if test="sex!=null">
            and sex=#{sex}
        </if>
        </where>
    </select>
  1. 方法一:“<“符号在xml中有特殊含义,所以可以用&lt转义字符代替,但是可读性不太好;
    方法二:xml中的CDATA块中的特殊字符不用额外的转义;
    <select id="findByLt" parameterType="int" resultType="domain.Student">
        <![CDATA[
          select * from student where sid<1004
          ]]>
    </select>
  1. #{ }与${ } 的区别
  • #{ }:先把#{ }占位符用?替换,在后续代码中给?赋值;只能占位一个值,不能占位表名或列名以及关键字;
  • ${ }:直接把值拼接到sql语句中;由于底层拼接字符串所以可能存在sql注入攻击的安全隐患;
  • 尽量使用#{},只有当某些功能不能使用#{} 实现时,采用${}

Mapper接口

配置文件中

    <mappers>
        <!--指定映射接口的位置-->
        <mapper class="mapper.StudentMapper"/>
    </mappers>
@Insert 包括 @option
@Update
@Delete
@select

案例:

package mapper;

import domain.Student;
import org.apache.ibatis.annotations.*;

import java.util.List;
import java.util.Map;

public interface StudentMapper {

    @Insert("insert into student(sid,sname,birthday,sex) values(null,#{sname},#{birthday},#{sex})")
    @Options(useGeneratedKeys = true,keyProperty = "sid")
    void insert(Student student);

   	//需要动态生成sql语句
    void update(Student student);

    @Delete("delete from student where sid=#{sid}")
    void delete(int sid);

    @Select("select * from student")
    List<Student> findAll();

    @Select("select * from studnet where sid=#{sid}")
    Student findById(int sid);


    @Select("select * from student limit #{m} #{n}")
    List<Student> findByPage(Map map);
}

测试类

public class StudentMapperTest {
    static SqlSessionFactory factory;
    static {
        try {
            InputStream in = Resources.getResourceAsStream("mybatis-config.xml");
            factory =  new SqlSessionFactoryBuilder().build(in);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }


    @Test
    public void testInsert() {
        SqlSession sqlSession = factory.openSession();

        //获取接口
        StudentMapper mapper = sqlSession.getMapper(StudentMapper.class);
        Student stu = new Student();
        stu.setSname("小林");
        stu.setBirthday(new Date());
        stu.setSex("女");
        mapper.insert(stu);

        sqlSession.commit();
        sqlSession.close();
    }
}

原理:
mapper的类型,这个类是使用了jdk的动态代理技术生成的类,在代码运行期间生成;所以这个接口在调用mapper时在底层被实现;


public class $Proxy10 imlements StudentMapper{
	private Sqlsession sqlsessionl
	public $proxy10(Sqlsession sqlsession){
		this.sqlSession=sqlsession;
	}
	//其中sql语句从@Insert获得,参数对象就是student
	public void insert(Student student){
		sqlSession.insert(sql,参数对象)
	}
}

缺点

  1. 接口方法不能直接应用多个方法参数;
    解决方法:
    1) 使用map集合来传递参数,每个参数对应map中的一个键值对;
    2 ) 用@Param注解

  2. Mapper接口中不能有方法的重载;

  3. 使用Mapper接口方式实现动态方式比较复杂;接口和xml文件结合使用;
    案例:动态更新数据库
    在接口中定义方法,因为需要使用xml,所以不用注解;

void update(Student student);

创建与接口同名包同名文件的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(命名空间) 用来防止sql的命名冲突的-->
<mapper namespace="mapper.StudentMapper">
    <delete id="deleteByIds" parameterType="list">
        delete from student where sid in
        <foreach collection="list" item="x" open="(" close=")" separator=",">
            #{x}
        </foreach>
    </delete>

    <update id="update" parameterType="domain.Student">
        update student
        <set>
            <if test="sname!=null">
                sname=#{sname},
            </if>
            <if test="birthday!=null">
                birthday=#{birthday},
            </if>
            <if test="sex!=null">
                sex=#{sex}
            </if>
        </set>
        where sid=#{sid}
    </update>
</mapper>

需要同步的地方
在这里插入图片描述

复杂sql映射

高级映射

案例1:表名和属性名不一致时 如何映射(除了起别名)例如属性名为sname,表列名为name;

解决方法:
resultMap是在需要复杂映射时,自己来定义更详细的映射方式;告诉Mybatis数据表列和对象属性的对应关系;
如果属性名和列名一致,则可以不用显示映射,但是如果像案例2那样存在嵌套映射则不能省略,可以添加``

    <!--自定义的映射关系-->
    <resultMap id="b" type="domain.Student">
        <id column="id" property="id"></id>
        <result column="sname" property="name"></result>
    </resultMap>

案例2:查询一级模块,每个一级模块又有自己对应的二级子模块;
注意新增了接口,要在配置文件中添加该接口

<mapper class="mapper.ModuleMapper"/>

表信息

+----+----------+-----+--------------------+
| id | name     | pid | code               |
+----+----------+-----+--------------------+
|  1 | 系统管理 |   0 |                    |
|  2 | 订单管理 |   0 |                    |
|  3 | 商品管理 |   0 |                    |
| 11 | 邮件设置 |   1 | /system/email      |
| 12 | 短信设置 |   1 | /system/sms        |
| 13 | 用户管理 |   1 | /system/user       |
| 14 | 权限分配 |   1 | /system/role       |
| 21 | 查询订单 |   2 | /order/search      |
| 22 | 退单处理 |   2 | /order/refund      |
| 23 | 统计分析 |   2 | /order/stat        |
| 31 | 查询商品 |   3 | /product/search    |
| 32 | 上下架   |   3 | /product/onoff     |
| 33 | 统计分析 |   3 | /product/stat      |
| 34 | 库存管理 |   3 | /product/inventory |
+----+----------+-----+--------------------+

方法一:
先查询一级模块,再根据一级module的id查新二级Module;N+1次查询

<!--namespace(命名空间) 用来防止sql的命名冲突的-->
<mapper namespace="mapper.ModuleMapper">
    <select id="selectModule" resultMap="b">
      select id,name,pid,code from rbac_module where pid=0
    </select>

    <!--自定义的resultMap映射map
    id是select生命的map名
    type为查找出来的对象-->
    <resultMap id="b" type="domain.Module">

        <!--主键:column:一级module表的列名
        property:一级module对象id-->
        <id column="id" property="id"></id>
        <!--其他列-->
        <result column="name" property="name"></result>
        <result column="pid" property="pid"></result>
        <result column="code" property="code"></result>
        <!--一级的children属性是二级module集合-->
        <!--property:一级module的children属性
        select:查找二级module的select标签id
        column:根据一级mudule的id查找二级module-->
        <collection property="children" select="findChildren" column="id">
        </collection>
    </resultMap>

    <!--定义查找二级module的select标签-->
    <!--一级二级对应关系:二级的pid对象一级的id-->
    <select id="findChildren" parameterType="int" resultType="domain.Module">
        select id,name,pid,code from rbac_module where pid=#{pid}

    </select>


</mapper>

方法二:
使用表连接,将所有模块都一次性查询出来,再根据列名和属性进行映射;

连接后的表

+----+----------+-----+------+-----+----------+------+--------------------+
| id | name     | pid | code | bid | bname    | bpid | bcode              |
+----+----------+-----+------+-----+----------+------+--------------------+
|  1 | 系统管理 |   0 |      |  11 | 邮件设置 |    1 | /system/email      |
|  1 | 系统管理 |   0 |      |  12 | 短信设置 |    1 | /system/sms        |
|  1 | 系统管理 |   0 |      |  13 | 用户管理 |    1 | /system/user       |
|  1 | 系统管理 |   0 |      |  14 | 权限分配 |    1 | /system/role       |
|  2 | 订单管理 |   0 |      |  21 | 查询订单 |    2 | /order/search      |
|  2 | 订单管理 |   0 |      |  22 | 退单处理 |    2 | /order/refund      |
|  2 | 订单管理 |   0 |      |  23 | 统计分析 |    2 | /order/stat        |
|  3 | 商品管理 |   0 |      |  31 | 查询商品 |    3 | /product/search    |
|  3 | 商品管理 |   0 |      |  32 | 上下架   |    3 | /product/onoff     |
|  3 | 商品管理 |   0 |      |  33 | 统计分析 |    3 | /product/stat      |
|  3 | 商品管理 |   0 |      |  34 | 库存管理 |    3 | /product/inventory |
+----+----------+-----+------+-----+----------+------+--------------------+
<!--连接表查询,好处:只查询一次-->
    <select id="findAll" resultMap="c">
        select a.*,b.id bid,b.name bname,b.pid bpid,b.code bcode
        from rbac_module a inner join rbac_module b on a.id=b.pid
    </select>

    <resultMap id="c" type="domain.Module">
        <!--映射一级module的属性-->
        <id column="id" property="id"></id>
        <result column="name" property="name"></result>
        <result column="pid" property="pid"></result>
        <result column="code" property="code"></result>

        <!--ofType查询集合的类型-->
        <collection property="children" ofType="domain.Module">
            <!--映射二级module属性-->
            <id column="bid" property="id"></id>
            <result column="bname" property="name"></result>
            <result column="bpid" property="pid"></result>
            <result column="bcode" property="code"></result>
        </collection>
    </resultMap>

缓存

一级缓存
SqlSession自带缓存功能,缓存中没有才回去数据库中查询,否则直接返回缓存结果,称为一级缓存;缓存生命周期,SqlSession建立的时候开始缓存,直到SqlSession关闭,缓存清空;
每个SqlSession有自己独立的缓存并且互不干扰;

二级缓存
二级缓存是全局的,作用域整个SqlSession,需要额外设置,但可以允许多个SqlSession共享缓存数据;

好处:可以较大提升查询效率,避免了频繁访问数据库,在频繁增删改时不适合使用;
缺点:如果其他SqlSession修改了数据库的记录的时候缓存失效;

往二级缓存中存储时需要将对象序列化,取出来的时候需要反序列化;

  • 在xml mapper里加上<cache></cache>标签,表示启动二级缓存;
  • 接口mapper 二级缓存:在接口上添加一个@CacheNameSpace注解;

学习资料

深入学习Mybatis:

  • mybatis简介中文文档;(偏向基础)
  • 阅读Mybatis源代码
  • Mybatis-plus
  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值