MyBatis总结

MyBati总结

之前我学习的hibernate是一个全自动化的ORM实现,而mybatis是一个半自动化的ORM实现,hibernate连最基本的sql语句都不用写极为方便快捷,但是也存在着使用不够灵活的缺陷

MyBatis的项目配置

我这里首先创建了一个Maven工程,进行pom.xml的jar包导入

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>org.example</groupId>
    <artifactId>mybatisdemo2</artifactId>
    <version>1.0-SNAPSHOT</version>

    <dependencies>
        <dependency>
            <groupId>org.mybatis</groupId>
            <artifactId>mybatis</artifactId>
            <version>3.5.5</version>
        </dependency>
<!--        测试类需要jar包,便于测试-->
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.12</version>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>8.0.23</version>
        </dependency>
<!--        为实体类提供get、set方法、构造方法-->
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>1.18.18</version>
        </dependency>
    </dependencies>

</project>

jar包导入之后,创建出各个包的结构,注意这里我刚开始使用Maven进行备注

备注:所有的java源文件放在main下的java包下,所有的配置文件放在resources文件下,所有的测试文件放在test下的java包下
在这里插入图片描述

创建出这样的结构后,一个MyBatis工程框架就搭建好了。

配置mybatisconfig.xml文件

配置mybatisconfig.xml文件,可以到Mybatis官网找到配置的第一段话
在这里插入图片描述

然后就可以在mybatisconfig.xml文件中编写配置

在这里我把有关数据库的dirver,url,username,password通过db.properties文件提了出来,方便修改

db.properties

driver = com.mysql.cj.jdbc.Driver
url = jdbc:mysql://localhost:3306/bookborrow?allowPublicKeyRetrieval=true&useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone=GMT
username = yyb
password = root

mybatisconfig.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>
    <!-- 引入db.properties文件-->
    <properties resource="db.properties">
        <!--默认值:如果指定的资源文件中,不存在此属性时,自动使用这里的属性-->
        <property name="username" value="yyb"/>
    </properties>
<!--    控制台打印日志-->
    <settings>
        <setting name="logImpl" value="STDOUT_LOGGING"/>
        <!--数据库中的下划线命名自动转换为属性的驼峰命名法    sc_name =  scName -->
        <setting name="mapUnderscoreToCamelCase" value="true" />
    </settings>
<!--    给包配置-->
    <typeAliases>
        <package name="com.yyb.pojo"/>
    </typeAliases>
    <!--默认使用dev作为环境-->
    <environments default="dev">
        <environment id="dev">
            <transactionManager type="JDBC">
            </transactionManager>
            <!--
                POOLED:连接池
                UNPOOLED:不使用连接池
                JNDI : Java Naming  Directory Interface  容器(Tomcat)中连接池技术
            -->
            <dataSource type="POOLED">
                <property name="driver" value="${driver}"/>
                <property name="url" value="${url}"/>
                <property name="username" value="${username}"/>
                <property name="password" value="${password}"/>
            </dataSource>
        </environment>
    </environments>
    <!--配置映射文件-->
    <mappers>
        <mapper resource="mapper/booktype.xml"></mapper>
        <mapper resource="mapper/teacher.xml"></mapper>
        <mapper resource="mapper/bookinfo.xml"></mapper>
    </mappers>
</configuration>

创建实体类

package com.yyb.pojo;

import lombok.Data;

import java.util.Date;

/**
 * @Author FuGwaaa
 * @Date 2021/3/21 19:35
 * @Version 1.0
 */
@Data
public class BookInfo {
    private int bookId;
    private String bookCode;
    private String bookName;
    private int bookType;
    private String bookAuthor;
    private String publishPress;
    private String createdBy;
    private Date publishDate;
    private int isBorrow;
    private Date creationTime;
    private Date lastUpdatetime;
}

我这里创建实体类,写好属性之后利用之前pom.xml引入的lombok的jar包,注解标识实体类不用再去写get、set、toString方法

注意如果这里注解Data之后还是不能调用该类的get、set方法解决方法如下

file->settings->plugins->搜索lonbok插件->安装插件,安装完进行重启idea,就可以调用注解Data之后类的get/set方法
在这里插入图片描述

这个lombok还可以有其他用处,比如生成有参的构造方法,注意使用了@AllArgsConstructor有参构造方法注解之后要记得@NoArgsConstructor补上无参的构造方法,不然无参构造方法不会自动生成

编写dao层接口

package com.yyb.dao;

import com.yyb.pojo.BookType;
import org.apache.ibatis.annotations.Param;

import java.util.List;

/**
 * @Author FuGwaaa
 * @Date 2021/3/21 15:34
 * @Version 1.0
 */
public interface IBookTypeDao {
    void save(BookType bookType);
    void delete(Integer id);
    void update(BookType bookType);
    List<BookType> queryAll();
    BookType query(BookType bookType);
    void getCount();
    //通过注解传参数
    List<BookType> serach(@Param("id") int id ,@Param("name") String name);
}

这里的dao层就是正常的dao层接口的编写、重点在mapper映射

编写mapperSQL映射

mapper相当于是写sql语句的地方

<?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.yyb.dao.IBookTypeDao">
    <select id="getCount" resultType="int">
        select count(*) from book_type
    </select>
    <select id="queryAll" resultType="BookType">
        select id,type_name from book_type
    </select>
    <update id="update" parameterType="BookType">
        update book_type set type_name=#{type_name} where id = #{id}
    </update>
    <insert id="save" parameterType="booktype">
        insert into book_type(id,type_name) values (#{id},#{type_name})
    </insert>
    <select id="query" parameterType="booktype" resultType="booktype">
        select id,type_name from book_type where id=#{id}
    </select>
    <delete id="delete">
        delete from book_type where id = #{id}
    </delete>
    <select id="serach" resultType="booktype">
        select id,type_name from book_type where id=#{id} and type_name = #{name}
    </select>
</mapper>

注意:

  • mapper标签的namespace属性一定要与实现的接口保持一致
  • 如果传参进来是String类型或者是基本数据类型,则不用写parameterType,否则要写传入参数的类型
  • sql语句中的#{参数}语句相当于是当前位置被?代替,不能进行字符串的拼接,如果要进行字符串拼接,则使用 参 数 这 个 符 号 占 位 符 可 以 惊 醒 字 符 串 拼 接 , 例 如 要 进 行 模 糊 查 询 , 传 参 进 来 是 ′ 大 ′ , 要 写 s q l 语 句 时 应 该 为 l i k e ′ _ {参数}这个符号占位符可以惊醒字符串拼接,例如要进行模糊查询,传参进来是'大',要写sql语句时应该为 like '\_ ,sqllike_{参数}_’,进行模糊查询;如果要使用#{},则应当在传进参数之前就完成字符拼接。

测试

import com.yyb.pojo.BookType;
import com.yyb.utils.MybatisUtil;
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
import org.junit.Test;

import java.io.IOException;
import java.io.InputStream;
import java.util.List;

/**
 * @Author FuGwaaa
 * @Date 2021/3/21 10:24
 * @Version 1.0
 */

public class Test01 {
    @Test
    public void queryOne(){
        SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
        InputStream is = this.getClass().getResourceAsStream("mybatisconfig.xml");
        //build(io流,选择环境名称),可以不用默认的环境,用指定id的环境
        SqlSessionFactory fa = builder.build(is);
        SqlSession sqlSession = fa.openSession();
        Integer getCount = sqlSession.selectOne("getCount");
        System.out.println(getCount);
    }
    @Test
    public void queryAll(){
        SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
        try {
            InputStream is = Resources.getResourceAsStream("mybatisconfig.xml");
            SqlSessionFactory factory = builder.build(is);
            SqlSession session = factory.openSession(true);
            List<BookType> queryAll = session.selectList("queryAll");
            queryAll.forEach((x)-> System.out.println(x.getId()+"===="+x.getType_name()));
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    @Test
    public void update(){
        SqlSession session = MybatisUtil.getSession();
        BookType bookType = new BookType();
        bookType.setId(6);
        bookType.setType_name("不惜代价");
        int updateName = session.update("updateName", bookType);
        System.out.println(updateName);
    }
}

MyBatis的核心对象

  1. SqlSessionFactoryBuilder

  2. SqlSessionFactory

  3. SqlSession
    在这里插入图片描述

String resource = "mybatis-config.xml"; 
//得到输入流
InputStream is = Resources.getResourceAsStream(resource);  
//新建SqlSessionFactoryBuilder之后buildesqlSessionFactory
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(is);
//这里的autoCommit是是否自动提交事务,默认是否
SqlSession session = sqlSessionFactory.openSession(boolean autoCommit);
//记得在使用之后关闭session.close()

这里我用到了junit包,用到了@Test注解,可以直接进行测试,其中有一种方法断言,可以进行结果判断,如果为真则正常运行,断言成功,如果结果为假则报错,断言失败

@Test
    public void ass(){
        int a = 1+1;
        Assert.assertEquals(1,a);
    }

在这里插入图片描述

这里断言他是1,实际上为2,断言失败

在这里使用了list的另一种遍历,lambda表达式

list.forEach((x)->System.out.println(x))

在这里测试成功之后,为了方便我们把获得session的操作封装为一个工具类

封装MybatisUtil

package com.yyb.utils;

import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;

import java.io.IOException;
import java.io.InputStream;

/**
 * @Author FuGwaaa
 * @Date 2021/3/21 14:34
 * @Version 1.0
 */
public class MybatisUtil {
    private static SqlSessionFactoryBuilder builder = null;
    private static SqlSessionFactory factory = null;
    static {
        getfactory();
    }
    public static void getfactory(){
        builder = new SqlSessionFactoryBuilder();
        try {
            InputStream is = Resources.getResourceAsStream("mybatisconfig.xml");
            factory = builder.build(is);
        } catch (
                IOException e) {
            e.printStackTrace();
        }
    }

    public static SqlSession getSession(){
        //这里的true为自动提交事务,默认为false
        return factory.openSession(true);
    }

}

封装之后的测试

package com.yyb.test;

import com.yyb.dao.IBookTypeDao;
import com.yyb.pojo.BookType;
import com.yyb.utils.MybatisUtil;
import org.apache.ibatis.session.SqlSession;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;

import java.util.List;

/**
 * @Author FuGwaaa
 * @Date 2021/3/21 15:32
 * @Version 1.0
 */
public class TestMybatis {
    private SqlSession session = null;
    @Before
    public void before(){
        session = MybatisUtil.getSession();
    }
    @After
    public void after(){
        session.close();
    }
    @Test
    public void getCount(){
        IBookTypeDao iBookTypeDao = session.getMapper(IBookTypeDao.class);
        iBookTypeDao.getCount();
    }
    @Test
    public void delete(){
        IBookTypeDao mapper = session.getMapper(IBookTypeDao.class);
        mapper.delete(6);
    }
    @Test
    public void queryAll(){
        IBookTypeDao mapper = session.getMapper(IBookTypeDao.class);
        List<BookType> bookTypes = mapper.queryAll();
        bookTypes.forEach((x)-> System.out.println(x));
    }
    @Test
    public void query(){
        IBookTypeDao mapper = session.getMapper(IBookTypeDao.class);
        BookType bookType = new BookType();
        bookType.setId(1);
        BookType query = mapper.query(bookType);
        System.out.println(query);
    }

    @Test
    public void serach(){
        IBookTypeDao mapper = session.getMapper(IBookTypeDao.class);
        List<BookType> serach = mapper.serach(1, "社科类");
        System.out.println(serach);
    }
}

封装完之后更为方便,直接获取到session,@before、@after分别是在测试之前和测试之后自动调用的方法

关联映射

关联映射
  一对一  账户信息---个人详细
  多对一  Student ->- School
  一对多  School ->-- Student
  多对多  Book--- Author

  class Book{  
  //多对多
    Set<Author> set;
  }
  class Author{
  //多对多
    Set<Book> set;
  }

  class Student{
    //多对一
    School school;
  }

  class School{
  //一对多
    Set<Student> set = new HashSet<>();
  }

1.内部嵌套映射

这里我们用书类型表book_type和书目表book_info做出一对多,和多对一的关联映射的例子

1.用book_info多对一映射查出所有的书类型及书目

首先,BookInfo实体类中要引入BookType类,如下

package com.yyb.pojo;

import lombok.Data;

import java.util.Date;

/**
 * @Author FuGwaaa
 * @Date 2021/3/21 19:35
 * @Version 1.0
 */
@Data
public class BookInfo {
    private int bookId;
    private String bookCode;
    private String bookName;
    private int bookType;
    private String bookAuthor;
    private String publishPress;
    private String createdBy;
    private Date publishDate;
    private int isBorrow;
    private Date creationTime;
    private Date lastUpdatetime;
    private BookType bType;
}

然后在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">
    <!--namespace要和对应的dao接口名字一致-->
<mapper namespace="com.yyb.dao.IBookInfoDao">
    <!--给字段取个名字,方便使用-->
    <sql id="column">
  book_id,book_code,book_name,book_type,book_author,publish_press,publish_date,is_borrow,createdBy,creation_time,last_updatetime
    </sql>
    <!--创建结果映射‘bookinfo’,类型为’bookinfo‘,自动映射名字相同的属性和列-->
    <resultMap id="bookinfo" type="bookinfo" autoMapping="true">
    <!--创建元素bType,这个名字要与实体类中的名字一致,主键为id,类型为booktype,自动映射名字相同的属性和列-->
        <association property="bType" column="id" javaType="booktype" autoMapping="true">
            <!--对于使用内或外连接查询,内部映射起作用的,但是如果需要引用其他已经配置的映射时,
       可以使用 resultMap="com.yyb.dao.IBooktypeDao.typeMap"-->
        </association>
    </resultMap>
    //多表联查
    <select id="queryAll" resultMap="bookinfo">
        select
        <include refid="column"></include>,bt.id,bt.type_name
        from book_info bi left join book_type bt
        on bi.book_type = bt.id
    </select>
</mapper>

2.用book_type一对多映射查出所有的书类型的数量

首先,BookType实体类中要引入BookInfo类,如下

package com.yyb.pojo;

import lombok.Data;

import java.util.HashSet;
import java.util.Set;

/**
 * @Author FuGwaaa
 * @Date 2021/3/21 10:19
 * @Version 1.0
 */
@Data
public class BookType {
    private int id;
    private String typeName;
    private Set<BookInfo> bookInfos = new HashSet<>();
}

然后映射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">
<!--namespace要求必须和要实现的接口保持一致-->
<mapper namespace="com.yyb.dao.IBookTypeDao">
    <resultMap id="bookTypeMap" type="BookType" autoMapping="true">
        <!--在这里一定要直顶id,如果不指定id就会出现,查询多条结果id相同的没有合并到一起的情况
			例如:计算机类书籍2本,就会查出计算机---1,计算机---1的情况-->
        <id property="id" column="id"></id>
        <collection property="bookInfos" ofType="BookInfo" column="bookId" autoMapping="true"></collection>
    </resultMap>
    <!--在这里一定要用resultMap,不能用resultType否则就会出现找不到bookTypeMap的错误-->
    <select id="queryAll" resultMap="bookTypeMap">
        select
        bt.id,bt.type_name,bi.book_id,bi.book_code,bi.book_name,bi.book_type,bi.book_author,bi.publish_press,bi.publish_date,bi.is_borrow,bi.createdBy,bi.creation_time,bi.last_updatetime
        from book_type bt left join book_info bi
        on bt.id = bi.book_type
    </select>
</mapper>

2.内部嵌套查询(N+1内嵌查询方式)

这里我们依旧用用书类型表book_type和书目表book_info做出一对多,和多对一的关联映射的例子,实体类就不再重复写

1.用book_info多对一映射查出所有的书类型及书目

bookinfo.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">
<mapper namespace="com.yyb.dao.IBookInfoDao">
    <!---可重复使用的sql语句-->
    <sql id="column">
  book_id,book_code,book_name,book_type,book_author,publish_press,publish_date,is_borrow,createdBy,creation_time,last_updatetime
    </sql>
    <!--id是自己起的名字,type为返回的实体类-->
    <resultMap id="infoMap" type="bookinfo" autoMapping="true">
        <!--property必须与返回实体类中对应的对象名字相同,column这里是上层查出来作为下层查询条件的列名,select为下层查询的id,fetchType为lazy时如果不需要查询下层,则不查询,提高查询效率,如果为eager或者不填为默认值时,都会全部查询-->
        <association property="bType" column="book_type" select="gettype" fetchType="lazy"></association>
        <!--注意:
       对于N+1内部嵌套查询方式,association内部映射无效
    -->
    </resultMap>
    <resultMap id="typeMap" type="booktype" autoMapping="true"></resultMap>
    <select id="get" resultMap="infoMap">
        select
        <include refid="column"></include>
        from book_info
        where book_id = #{bookId}
    </select>

    <select id="gettype" resultMap="typeMap">
        select id,type_name
        from book_type
        where id = #{bookType}
    </select>
</mapper>

测试类在测试使用get方法时,我这里使用了懒抓取,所以如果查询结果不需要book_type中的信息时就不用查询gettype,反之,如果需要book_type中的信息就会自动查询,这样就不会每次都连表查询,浪费查询效率。

config.xml文件中的settings中可以这样设置,配置resultMap时候自动匹配;全局的抓取策略,是否懒加载

<!--
 NONE
禁止自动匹配
PARTIAL(默认)
自动匹配所有属性,内部嵌套除外
FULL
自动匹配所有
-->
<setting name="autoMappingBehavior" value="FULL" />
<!--默认的抓取策略 是否延时加载-->
<setting name="lazyLoadingEnabled" value="false"/>

2.用book_type一对多映射查出所有的书类型的数量

booktype.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.yyb.dao.IBookTypeDao">
    <resultMap id="typeMap" type="booktype" autoMapping="true">
        <id property="id" column="id"></id>
        <!--匹配对应的set集合,peoperty要和booktype中的set集合名字对应;oftype与集合里的元素类型对应,column这里是上层查出来作为下层查询条件的列名,select为下层查询的id,fetchType为lazy时如果不需要查询下层,则不查询,提高查询效率,如果为eager或者不填为默认值时,都会全部查询-->
        <collection property="bookInfos" ofType="bookinfo" column="id" select="getinfo" fetchType="lazy"></collection>
    </resultMap>
    <resultMap id="infoMap" type="bookinfo" autoMapping="true"></resultMap>
    <select id="get" resultMap="typeMap">
        select id,type_name
        from book_type
        where id = #{id}
    </select>

    <select id="getinfo" resultMap="infoMap">
        select book_id,book_code,book_name,book_type,book_author,publish_press,publish_date,is_borrow,createdBy,creation_time,last_updatetime
        from book_info
        where book_type = #{id}
    </select>
</mapper>

测试类在测试使用get方法时,我这里使用了懒抓取,所以如果查询结果不需要book_info中的信息时就不用查询getinfo,反之,如果需要book_info中的信息就会自动查询,这样就不会每次都连表查询,浪费查询效率。

缓存

mybatis自带一级缓存和二级缓存

1.一级缓存

  • 默认一级缓存是开启的。不能关闭的
  • Mybatis的一级缓存是指Session缓存。一级缓存的作用域默认是一个SqlSession。也就是在同一个SqlSession中
  • 注意:flushCache=“false”;如果查询不用缓存数据,则flushCache=“true”
  • 先查二级缓存,再查一级缓存,再查数据库;即使在一个sqlSession中,也会先查二级缓存;

缓存失效情况:

  • 1.查询时,映射方法中的flushCache=“true”

  • 2.sqlSession.clearCache();

  • 3.执行了任意的更新操作:修改 删除 插入

  • 4.事务的回滚 和 提交

  • 5.不同的命名空间,不同的sql,不同的SqlSession都不共享缓存

  • 6.sqlSessesion.close()

2.二级缓存

首先在配置文件中设置settings

<!--是否开启二级缓存-->
<setting name="cacheEnabled" value="true"/>

我测试了下,没有这句话也可以开启二级缓存,默认应该是true

其次,在mapper映射文件中加入<cache/>,开启本映射的二级缓存,如果在某一个方法映射中设置了useCache="true"参数,会开启此方法的二级缓存,mapper开启二级缓存时候方法二级缓存默认开启

最后,设置对应的实体类为实现可序列换接口implements Serializable

做出测试

@Test
    public void testTwo(){
        IBookTypeDao mapper = session.getMapper(IBookTypeDao.class);
        List<BookType> bookTypes = mapper.queryAll();
        session.close();
         session = MybatisUtil.getSession();
        IBookTypeDao mapper1 = session.getMapper(IBookTypeDao.class);
        List<BookType> bookTypes2 = mapper1.queryAll();

        List<BookType> bookTypes1 = mapper1.queryAll();
        System.out.println(bookTypes2==bookTypes1);

        System.out.println(bookTypes);
        System.out.println(bookTypes1);
        BookType b1 = bookTypes.get(1);
        BookType b2 = bookTypes2.get(1);
        System.out.println(b1==b2);

    }

运行结果
在这里插入图片描述

这里我们可以看到,访问数据库只访问了一次,其余两次都是从缓存中拿出来的,对应的缓存命中率为0.5,0.66三次查询命中2次缓存

但是这里我们可以看到我对比了两次查出来的结果,发现地址并不相同,取出其中的属性作一比较发现地址也不同,但是都是在缓存中存的对象,取出来地址应该是一样的,为什么不一样,经过查找资料未果,询问大佬之后给出了这样的解释:

因为二级缓存实体类对象需要被序列化,在第一次访问数据库查出来之后,先存入一级缓存,再存入二级缓存,而二级缓存序列化之后,每次命中二级缓存之后,要反序列化出来,所以每次反序列化之后都不是同一个对象,地址自然不同,而一级缓存没有序列化,直接从内存上取出来,所以地址一样;

除了用自带的二级缓存,我们还可以用mybatis提供的ehcache

首先在pom中引入依赖

<dependency>
    <groupId>org.mybatis.caches</groupId>
    <artifactId>mybatis-ehcache</artifactId>
    <version>1.2.1</version>
</dependency>

然后在映射文件中配置

<cache type="org.mybatis.caches.ehcache.EhcacheCache"></cache>

这里是不用将实体类实现可序列化的

再次测试
在这里插入图片描述

再次测试之后我们发现结果一样,但是对比查出来的结果,地址也一样,说明没有序列化,实体类也不要求序列化,和自带的二级缓存不同

动态SQL

if 标签

if 标签通常用于 WHERE 语句、UPDATE 语句、INSERT 语句中,通过判断参数值来决定是否使用某个查询条件、判断是否更新某一个字段、判断是否插入某个字段的值。

<if test="name != null and name != ''">
    and NAME = #{name}
</if>

foreach 标签

foreach 标签主要用于构建 in 条件,可在 sql 中对集合进行迭代。也常用到批量删除、添加等操作中。

<!-- in查询所有,不分页 -->
<select id="selectIn" resultMap="BaseResultMap">
    select name,hobby from student where id in
    <foreach item="item" index="index" collection="list" open="(" separator="," close=")">
        #{item}
    </foreach>
</select>

属性介绍:

  • collection:collection 属性的值有三个分别是 list、array、map 三种,分别对应的参数类型为:List、数组、map 集合。(必须指定)
  • item :表示在迭代过程中每一个元素的别名
  • index :表示在迭代过程中每次迭代到的位置(下标)
  • open :前缀
  • close :后缀
  • separator :分隔符,表示迭代时每个元素之间以什么分隔

choose 标签

有时候我们并不想应用所有的条件,而只是想从多个选项中选择一个。MyBatis 提供了 choose 元素,按顺序判断 when 中的条件出否成立,如果有一个成立,则 choose 结束。当 choose 中所有 when
的条件都不满则时,则执行 otherwise 中的 sql。类似于 Java 的 switch 语句,choose 为 switch,when 为 case,otherwise 则为 default。

if 是与(and)的关系,而 choose 是或(or)的关系。

<select id="getStudentListChoose" parameterType="Student" resultMap="BaseResultMap">
    SELECT * from STUDENT WHERE 1=1
    <where>
        <choose>
            <when test="Name!=null and student!='' ">
                AND name LIKE CONCAT(CONCAT('%', #{student}),'%')
            </when>
            <when test="hobby!= null and hobby!= '' ">
                AND hobby = #{hobby}
            </when>
            <otherwise>
                AND AGE = 15
            </otherwise>
        </choose>
    </where>
</select>

where 标签

当 if 标签较多时,这样的组合可能会导致错误。 如下:

<select id="getStudentListWhere" parameterType="Object" resultMap="BaseResultMap">
    SELECT * from STUDENT WHERE
    <if test="name!=null and name!='' ">
        NAME LIKE CONCAT(CONCAT('%', #{name}),'%')
    </if>
    <if test="hobby!= null and hobby!= '' ">
        AND hobby = #{hobby}
    </if>
</select>

当 name 值为 null 时,查询语句会出现 “WHERE AND” 的情况,解决该情况除了将"WHERE"改为“WHERE 1=1”之外,还可以利用 where
标签。这个“where”标签会知道如果它包含的标签中有返回值的话,它就插入一个‘where’。此外,如果标签返回的内容是以 AND 或 OR 开头的,则它会剔除掉。

<select id="getStudentListWhere" parameterType="Object" resultMap="BaseResultMap">
    SELECT * from STUDENT
    <where>
        <if test="name!=null and name!='' ">
            NAME LIKE CONCAT(CONCAT('%', #{name}),'%')
        </if>
        <if test="hobby!= null and hobby!= '' ">
            AND hobby = #{hobby}
        </if>
    </where>
</select>

set 标签

没有使用 if 标签时,如果有一个参数为 null,都会导致错误。当在 update 语句中使用 if 标签时,如果最后的 if 没有执行,则或导致逗号多余错误。使用 set 标签可以将动态的配置 set
关键字,和剔除追加到条件末尾的任何不相关的逗号。

<update id="updateStudent" parameterType="Object">
    UPDATE STUDENT
    SET NAME = #{name},
    MAJOR = #{major},
    HOBBY = #{hobby}
    WHERE ID = #{id};
</update>

<update id="updateStudent" parameterType="Object">
    UPDATE STUDENT SET
    <if test="name!=null and name!='' ">
        NAME = #{name},
    </if>
    <if test="hobby!=null and hobby!='' ">
        MAJOR = #{major},
    </if>
    <if test="hobby!=null and hobby!='' ">
        HOBBY = #{hobby}
    </if>
    WHERE ID = #{id};
</update>

使用 set+if 标签修改后,如果某项为 null 则不进行更新,而是保持数据库原值。

<update id="updateStudent" parameterType="Object">
    UPDATE STUDENT
    <set>
        <if test="name!=null and name!='' ">
            NAME = #{name},
        </if>
        <if test="hobby!=null and hobby!='' ">
            MAJOR = #{major},
        </if>
        <if test="hobby!=null and hobby!='' ">
            HOBBY = #{hobby}
        </if>
    </set>
    WHERE ID = #{id};
</update>

trim 标签

trim标记是一个格式化的标记,主要用于拼接sql的条件语句(前缀或后缀的添加或忽略),可以完成set或者是where标记的功能。

trim属性主要有以下四个

  • prefix:在trim标签内sql语句加上前缀
  • suffix:在trim标签内sql语句加上后缀
  • prefixOverrides:指定去除多余的前缀内容,如:prefixOverrides=“AND | OR”,去除trim标签内sql语句多余的前缀"and"或者"or"。
  • suffixOverrides:指定去除多余的后缀内容。
<update id="updateByPrimaryKey" parameterType="Object">
	update student set 
	<trim  suffixOverrides=",">
		<if test="name != null">
		    NAME=#{name},
		</if>
		<if test="hobby != null">
		    HOBBY=#{hobby},
		</if>
	</trim> 
	where id=#{id}
</update>

关于动态sql中的大于小于

我在使用动态sql拼接时,发现大于号,小于号,或者大于等于,小于等于有时候不能用,会出现编译错误,最后发现因为写在xml映射文件中,所以有时候>,<会产生其一,所以一般使用转义

	&							&amp;		
     <							&lt;
	 >							&gt;
	 "							&quot;  //双引号
     '							&apos;  //单引号
   a<=b                 	a &lt;=b 或者 a <![CDATA[<= ]]>b 
   a>=b                 	a &gt;=b 或者 a <![CDATA[>= ]]>b
   a!=b						a <![CDATA[ <> ]]>b 或者 a <![CDATA[!= ]]>b

遇到的一个问题

在mybatis学习过程中我碰到了一个问题,纠结了很久在此做一记录
在这里插入图片描述
在这里插入图片描述

我这里的目的是想要查出,部门表中的员工个数,如果没有员工则显示0

经过测试之后,结果如下
在这里插入图片描述

在这里插入图片描述

我的采购部是没有人数的,但是他显示采购部人数为1,数据库查出数据也都为null

我将所有数据打印出来发现,采购部有一条数据,只有部门号
在这里插入图片描述

最后,经过数小时的纠结,我做出了这样的改动,解决了这个问题

我发现应该是部门表中的deptno和员工表中的deptno重名导致,mybatis无法分清,所以在数据库起名时最好带上表名前缀,例如dept_deptno

还有一种方法也可以解决,将连接查询时的主表放在查询列表的后面,也可以解决这个问题
在这里插入图片描述

在这里插入图片描述

类型转换器

内置类型转换器

在这里插入图片描述

自定义类型转换器

首先先编写一个类型转换器,关于类型转换器有两种方法

  • 实现TypeHandler<T>接口,T写的是要转换的java属性类型
  • 继承BaseTypeHandler<T>
public class IsBorrowType implements TypeHandler<String> {
    @Override
    public void setParameter(PreparedStatement preparedStatement, int i, String s, JdbcType jdbcType) throws SQLException {
        if (s.equals("已借阅")){
            preparedStatement.setInt(i,0);
        }else {
            preparedStatement.setInt(i,1);
        }
    }

    @Override
    public String getResult(ResultSet resultSet, String s) throws SQLException {
        int borrow = resultSet.getInt(s);
        return borrow==0?"已借阅":"未借阅";
    }

    @Override
    public String getResult(ResultSet resultSet, int i) throws SQLException {
        return null;
    }

    @Override
    public String getResult(CallableStatement callableStatement, int i) throws SQLException {
        return null;
    }
}

局部类型转换器配置

a.查询:在<resultmap>中
<result column=“gender” property=“gender” typeHandler=“com.wdzl.typehld.GenderTypeHandler”>
</result>

​ b.保存:在 SQL 中
​ #{gender,typeHandler=com.wdzl.typehld.GenderTypeHandler}

全局类型转换器配置

配置文件中
<typeHandler handler=“com.wdzl.typehld.PointTypeHandler”
javaType=“com.wdzl.entity.Point”
jdbcType=“VARCHAR”>
</typeHandler>

在映射文件中:
#{point,javaType=“com.wdzl.entity.Point”,jdbcType=“VARCHAR”}

注解

MyBatis 起初的映射(SQL)配置基于 XML的 ,而到了 MyBatis 3提供了新的基于注解的配置

注解与xml映射对比

注解
在这里插入图片描述

xml映射

在这里插入图片描述

mybatis常用注解

  • @Insert:实现新增 和 xml中的 sql语法完全一样
  • @Update:实现更新
  • @Delete:实现删除
  • @Select:实现查询
  • @Result:实现结果集封装
  • @Results:可以与@Result 一起使用,封装多个结果集
  • @ResultMap:实现引用@Results 定义的封装
  • @One:实现一对一结果集封装
  • @Many:实现一对多结果集封装
  • @SelectProvider: 实现动态 SQL 映射
  • @ResultMap : 引用结果集合
  • @SelectKey : 获取最新插入

在配置文件引入方式

<mapper class=“com.wdzl.dao.ISchoolDao”/>

@ResultMap

@Select("select scid,scname,address from school where scid=#{scid}")
@Results(id = "schoolMap",value= {
            @Result(id = true,column = "scid",property = "scid"),
            @Result(column = "scname",property = "schoolName"),
            @Result(column = "address",property = "address")
	}
)
public School get(Integer scid);

重用映射

@Select("select scid,scname,address from school where scid=#{scid}")
@Results(id = "schoolMap",value= {
            @Result(id = true,column = "scid",property = "scid"),
            @Result(column = "scname",property = "schoolName"),
            @Result(column = "address",property = "address")
	}
)
public School get(Integer scid);
 
@ResultMap(value = "schoolMap")  
@Select("select scid,scname,address from school")
public List<School> queryAll();

使用注解@SelectKey获得自动增长的主键

/**
     * 注意:before=false
      */
    @Insert("insert into school(scname,address)values
       (#{schoolName},#{address})")
    @SelectKey(before = false,keyColumn = "scid",
       keyProperty = "scid",
      statement = "select last_insert_id()",
      resultType = Integer.class)
    public Integer save(School school);

@param

/**
     * 根据主键删除学校
     *  注意#{cid} 和 @Param("cid") 对应
     */

    @Delete("delete from school where scid=#{cid}")
    public void delete(@Param("cid") Integer scid);

@One

@Results(id = "studentMap",value={
            @Result(id = true,property = "sid",column = "sid"),
            @Result(property = "sname",column = "sname"),
            @Result(property = "school",column = "scid",
   one=@One(select = "com.wdzl.dao.ISchoolDao.get" ))
    })
    @Select("select sid,sname,birthday,scid from student where sid=#{sid}")
    public Student get(Integer sid);

@Many

 @Select("select scid,scname,address from school where scid=#{scid}")
    @Results(id = "schoolMap",value= {
            @Result(id = true,column = "scid",property = "scid"),
            @Result(column = "scname",property = "schoolName"),
            @Result(column = "scid",property = "students",
  many = @Many(select = "com.*.IStudentDao.findBySchool"))
    }
    )
    public School get(Integer scid);

注解的动态SQL

@Update("<script>update school " +
            "<set>" +
            "  <if test=\"schoolName!=null\">" +
            "       sCname=#{schoolName}," +
            "  </if>" +
            "  <if test=\"address!=null\">" +
            "       address=#{address}," +
            "  </if>" +
            "</set>" +
            "<where>" +
            " <if test=\"scid!=null\">" +
            "   scid=#{scid}" +
            " </if>" +
            "</where>" +
            "</script>")
    public void update(School school);

使用Provider注解标识

@InsertProvider、@SelectProvider、@UpdateProvider、@DeleteProvider

(1) 先定义 Provider 类
   new SQL(){
            {
                SELECT("sid,sname,sex");
                FROM("student");
                if(student.getSname()!=null){
                    WHERE("sname=#{sname}");
                }
                ORDER_BY("birthday desc");
            }
        }.toString();

(2) 注解使用Provider类
    @SelectProvider(type = StudentProvider.class,method = "query")
    public List<Student> list(Student student);

MyBatis 执行原理

在这里插入图片描述

拦截器执行原理

在这里插入图片描述

拦截器接口拦截器实现类
Executor拦截执行器的方法CachingExecutor
StatementHandler拦截Sql语法构建处理RoutingStatementHandler
ParameterHandler拦截参数的处理DefaultParameterHandler
ResultSetHandler拦截结果集的处理DefaultResultSetHandler

自定义拦截器

  • 实现org.apache.ibatis.plugin.Interceptor
  • 添加@Intercepts注解,写上需要拦截的对象和方法,以及方法参数。
/**
*分页查询拦截器过程
*/
@Intercepts(
        @Signature(type = StatementHandler.class,method = "prepare",args = {Connection.class,Integer.class})
)
public class OrderInterceptor implements Interceptor{
    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        StatementHandler target = (StatementHandler)invocation.getTarget();
        BoundSql boundSql = target.getBoundSql();
        String sql = boundSql.getSql();
        String sqlCount = "select count(*) from ("+sql+") com";
        ParameterHandler ph = target.getParameterHandler();
        Connection connection = (Connection)invocation.getArgs()[0];
        PreparedStatement ps = connection.prepareStatement(sqlCount);
        ph.setParameters(ps);
        ResultSet resultSet = ps.executeQuery();
        int count = 0;
        if (resultSet.next()){
            count = resultSet.getInt(1);
        }
        PageBeanSQL pageBeanSQL = (PageBeanSQL) ph.getParameterObject();
        pageBeanSQL.setTotalRows(count);

        sql += " limit "+pageBeanSQL.getStartIndex()+","+pageBeanSQL.getRowsPerPage();
        Field sqlField = boundSql.getClass().getDeclaredField("sql");
        sqlField.setAccessible(true);
        sqlField.set(boundSql,sql);
        return invocation.proceed();
    }

    @Override
    public Object plugin(Object target) {
        System.out.println("======plugin=====");
        return Plugin.wrap(target,this);
    }

    @Override
    public void setProperties(Properties properties) {
        System.out.println("====setProperties==="+properties);
    }
}

配置文件中添加插件配置拦截器

<plugins>
    <plugin interceptor="com.yyb.interceptor.OrderInterceptor"></plugin>
</plugins>
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值