ORM 对象关系映射
image.png

先来配置并运行起一个Mybits吧

` 新建Maven工程,引入对应pom.xml文件

  • 首先将MySQL和IDEA进行关联,创建一个account表
    image.png
  • 创建一个对应的account的 java类
    image.png

调用原生接口

  • 创建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>
    <!-- 配置MyBatis运⾏环境 -->
    <environments default="development">
        <environment id="development">
            <!-- 配置JDBC事务管理 -->
            <transactionManager type="JDBC"></transactionManager>
            <!-- POOLED配置JDBC数据源连接池 -->
            <dataSource type="POOLED">
                <property name="driver" value="com.mysql.cj.jdbc.Driver"/>

                <property name="url"
                          value="jdbc:mysql://localhost:3306/wjh?useUnicode=true&amp;characterEncoding=UTF-8"></property>
                <property name="username" value="root"></property>
                <property name="password" value="root"></property>
            </dataSource>
        </environment>
    </environments>
</configuration>
  • 为Account类创建对应的Mapper.xml
    Mybits要求开发者为每一个实体类定义相应的Mapper.xml,并在其中定义管理该对象的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">
<mapper namespace="com.wjh.entity.Account">
    <insert id="save" parameterType="com.wjh.entity.Account">
                insert into wjh(username,password,age) value (#{username},#{password},#{age})
    </insert>
</mapper>
  • 在全局配置文件中注册Account.xml
   <mappers>
        <mapper resource="com.wjh.mapper.Mapper.xml"></mapper>
    </mappers>
  • 编写测试程序测试
public class Test {
    public static void main(String[] args) {
        //加载配置文件
        InputStream inputStream = Test.class.getClassLoader().getResourceAsStream("config.xml");
        SqlSessionFactoryBuilder sqlSessionFactoryBuilder = new SqlSessionFactoryBuilder();
        SqlSessionFactory sqlSessionFactory = sqlSessionFactoryBuilder.build(inputStream);
        SqlSession sqlSession = sqlSessionFactory.openSession();
        String statement = "com.wjh.mapper.AccountMapper.save";
        Account account = new Account(1L,"拔萝卜","12138",30);
        sqlSession.insert(statement,account);
        sqlSession.commit();

    }
}

添加成功截图
image.png

通过Mapper代理实现自定义接口

即创建接口实现类的代理类

  1. 创建自定义接口
package com.wjh.repository;

import com.wjh.entity.Account;

import java.util.List;

public interface AccountRepository {
    public int save(Account account);
    public int update(Account account);
    public int deleteByID( long id);
    public List<Account> findAll();
    public Account find(long id);
}
  1. 创建接口类自定义的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.wjh.repository.AccountRepository">
    <insert id="save" parameterType="com.wjh.entity.Account">
        insert into account(username,password,age) values (#{username},#{password},#{age})
    </insert>
<!--   这里的delete paraeterTyped应该为long -->
    <delete id="deleteByID" parameterType="long">
        delete from account where id=#{id}
    </delete>
    <update id="update" parameterType="com.wjh.entity.Account">
        update account set username = #{username},password = #{password}, age = #{age} where id = #{id}
    </update>
    <select id="find" parameterType="long" resultType="com.wjh.entity.Account">
        select * from account where id= #{id}
    </select>
    <select id="findAll"  resultType="com.wjh.entity.Account">
        select * from account
    </select>
</mapper>
  1. 在Mybatis的配置文件中注册
    <mappers>
<!--    这里切记使用/    -->
        <mapper resource="com/wjh/mapper/Mapper.xml"></mapper>
        <mapper resource="com/wjh/repository/AccountRepository.xml"></mapper>
    </mappers>
  1. 编写测试代码实现增删该查操作
package com.wjh.test;

import com.wjh.entity.Account;
import com.wjh.repository.AccountRepository;
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;
import java.util.List;

//测试调用接口实现类
public class Test2 {
    public static void main(String[] args) {
        InputStream inputStream = Test2.class.getClassLoader().getResourceAsStream("config.xml");
        SqlSessionFactoryBuilder sqlSessionFactoryBuilder = new SqlSessionFactoryBuilder();
        SqlSessionFactory sqlSessionFactory = sqlSessionFactoryBuilder.build(inputStream);
        SqlSession sqlSession = sqlSessionFactory.openSession();
        //与调用原生接口的主要区别体现在下面的代码处
        //掉用接口实现类的代理对象
        AccountRepository accountRepository = sqlSession.getMapper(AccountRepository.class);
        //增
//        Account account = new Account(4L,"老黑","5sad7667",8);
//        accountRepository.save(account);
//        sqlSession.commit();
//        sqlSession.close();
        //删
//        accountRepository.deleteByID(2L);
//        sqlSession.commit();
//        sqlSession.close();
        //改
//        Account account = new Account(4L,"狼哥","wsxxsdw667",8);
//        accountRepository.update(account);
//        sqlSession.commit();
//        sqlSession.close();
        //查
//        Account account = accountRepository.find(2L);
//        sqlSession.close();
//        System.out.println(account);
        List<Account> list = accountRepository.findAll();
        sqlSession.close();
        System.out.println(list);

    }
}

测试截图
image.png

及联查询

在自定义的resultMap中第一列通常是主键id,那么id 和result有什么区别呢?
id和result都是映射单列值到一个属性或字段的简单数据类型。
唯一不同的是,id是作为唯一标识的,当和其他对象实例对比的时候,这个id很有用,尤其是应用到缓存和内嵌的结果映射。

一对多

先了解一对多的及联查询后,多对多便大同小异,只是where语句中多借助了一个中间表
以Student Classes举例说明

  1. 创建对应的实体类
@Data

public class Student {
    private long id;
    private String name;
    private Classes classes;

    public Student() {
    }
}

@Data
@NoArgsConstructor
@AllArgsConstructor
public class Classes {
    private long id;
    private String name;
    private List<Student> students;
}

  1. 创建对应Repository接口
public interface StudentRepository {
    public int save(int id,String name,int cid);
    public Student findById(long id);
}

public interface ClassesRepository {

    public Classes findById(long id);
   
}

  1. 创建对应Map.xml
    在ClassRepository.xml的ResultMap配置中 标签中的第二个属性为ofType与StudentRepository,xml中的javaType不同,要注意区分。
<?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.wjh.repository.StudentRepository">
    <resultMap id="studentMap" type="com.wjh.entity.Student">
        <id column="id" property="id"></id>
        <result column="name" property="name"></result>
        <association property="classes" javaType="com.wjh.entity.Classes">
            <id column="cid" property="id"></id>
            <result column="cname" property="name"></result>
        </association>
    </resultMap>
    <insert id="save" >
        insert into student (id,name,cid) values (#{param1},#{param2},#{param3})

    </insert>
    <select id="findById" parameterType="long" resultMap="studentMap">
        select s.id,s.name,c.id cid,c.name cname from student as s,classes as c where s.cid = c.id and s.id = #{id}
    </select>
</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">
<mapper namespace="com.wjh.repository.ClassesRepository">
    <resultMap id="classMap" type="com.wjh.entity.Classes">
        <id column="cid" property="id"></id>
        <result column="cname" property="name"></result>
<!--    !!!!!!!!这里和StudentMap中就不一样,用的时ofType    -->
        <collection property="students" ofType="com.wjh.entity.Student">
            <id column="id" property="id"></id>
            <result column="name" property="name"></result>
        </collection>
    </resultMap>

    <select id="findById" parameterType="long" resultMap="classMap">
        select s.id,s.name,c.id cid,c.name cname from student as s,classes as c where s.cid = c.id and c.id = #{id}
    </select>

</mapper>

Mybatis逆向工程

本来需要挺多的代码,这里直接使用IDEA提供的Mybatis Generator插件,方便快捷
在以下界面选择相关目录,即可创建成功
image.png

在创建后要记得在mybatis的全局配置中添加mapper
image.png

一个查看Sql语句的插件(Mybatis Log)

先在全局配置中添加这句话
image.png
运行代码,即可在窗口中显现
image.png

Mybatis延迟加载

针对持久层的操作,根据不同的业务需求动态的查找所要用到的表;减少了不必要的查表次数,提升了效率
以student和classes这一组表举例说明。

  • 两表先分别建立一个findByIdLazy方法,并在map.xml中写相应的sql语句(只在单表中查询)
public interface StudentRepository {
    public int save(int id,String name,int cid);
    public Student findById(long id);
    public Student findByIdLazy(long id);
}

public interface ClassesRepository {

    public Classes findById(long id);
    public Classes findByIdLazy(long id);
   
}

<!--Student的-->
 
<!--懒加载-->
    <select id="findByIdLazy" parameterType="long" resultMap="studentMap">
        select  * from student as s where id=#{id}
    </select>
<!--Classes的-->
    <select id="findByIdLazy" parameterType="long" resultType="com.wjh.entity.Classes">
        select * from classes where id = #{id}
    </select>

  • 在student的association标签里添加 select 和 column属性
   <resultMap id="studentMap" type="com.wjh.entity.Student">
        <id column="id" property="id"></id>
        <result column="name" property="name"></result>
        <association property="classes" javaType="com.wjh.entity.Classes" select="com.wjh.repository.ClassesRepository.findByIdLazy" column="cid"></association>
    </resultMap>
  • 在全局配置中开启懒加载并开启打印sql语句
   <settings>
       <!-- 打印SQL-->
       <setting name="logImpl" value="STDOUT_LOGGING" />
       <!-- 开启延迟加载 -->
       <setting name="lazyLoadingEnabled" value="true"/>
   </settings>

懒加载结果验证

  1. 现在执行这条查询语句
    image.png
    可以看到执行了两条sql语句
    image.png

  2. 将测试代码换成这样
    image.png

可以发现只执行了一条sql语句查询
image.png

MyBatis缓存

1.mybatis的缓存有两种,分别是一级缓存和二级缓存。两者都属于查询缓存,也就是只有执行查询操作的时候才起缓存作用,对于增删改操作无效
2.一级缓存默认开启,二级缓存默认关闭。
3.两者都可以使用SqlSession对象的commit()方法更新缓存。(你肯定是对数据修改才会commit,而数据修改了,则缓存必然要更新,否则会发生类似"脏读"的现象)。
4.二级缓存分为mybatis自带的与第三方提供的两种。三方提供的比较常用的是ehcache。
5.二级缓存不同于一级缓存,前者是序列化操作,也就是说二级缓存是存放在硬盘中的。而一级缓存是存放在内存中的。

一级缓存

代码验证

//测试Mybatis缓存
public class Test5 {
    public static void main(String[] args) {
        InputStream inputStream = Test2.class.getClassLoader().getResourceAsStream("config.xml");
        SqlSessionFactoryBuilder sqlSessionFactoryBuilder = new SqlSessionFactoryBuilder();
        SqlSessionFactory sqlSessionFactory = sqlSessionFactoryBuilder.build(inputStream);
        SqlSession sqlSession = sqlSessionFactory.openSession();
        AccountRepository accountRepository = sqlSession.getMapper(AccountRepository.class);
        accountRepository.find(1);
        accountRepository.find(2);
        accountRepository.find(1);
        sqlSession.close();
        //要再开启一次sqlsession
         sqlSession = sqlSessionFactory.openSession();
        AccountRepository accountRepository2 = sqlSession.getMapper(AccountRepository.class);
        accountRepository2.find(2);
        sqlSession.close();
    }
}

执行以上代码观察sql语句执行情况,发现查询id=1的sql执行一次,查询id=2的sql语句查询两次
image.png

二级缓存

MyBatis自带的二级缓存,可简要分为三步:

  1. 在全局配置文件中添加打开二级缓存配置
  1. 在map.xml中添加标签
  2. 将实体类实现序列化接口
    验证
    同样执行上述代码,发现两条sql语句均执行一次
    image.png

第三方提供的缓存

配置较为固定,使用时现查即可。以encache为例,他不需要实例类实现序列化接口

MyBatis的动态SQL

  • where标签通常和if标签配合使用,使用where标签当检测到where和and直接相连时,删除and
  • choose标签里面包含多个when标签,配合使用和if标签效果差不多
  • trim标签中的 prefix 和 suffix 属性会被⽤于⽣成实际的 SQL 语句,会和标签内部的语句进⾏拼接,如果语句前后出现了 prefixOverrides 或者 suffixOverrides 属性中指定的值,MyBatis 框架会⾃动将其删除。
    使用实例1
<select id="findByAccount" parameterType="com.southwind.entity.Account"
resultType="com.southwind.entity.Account">
 select * from t_account
 <trim prefix="where" prefixOverrides="and">
 <if test="id!=0">
 id = #{id}
 </if>
 <if test="username!=null">
 and username = #{username}
 </if>
 <if test="password!=null">
 and password = #{password}
 </if>
 <if test="age!=0">
 and age = #{age}
 </if>
 </trim>

使用示例二

    insert into testmbg
    <trim prefix="(" suffix=")" suffixOverrides=",">
      <if test="name != null">
        `name`,
      </if>
      <if test="age != null">
        age,
      </if>
      <if test="hobby != null">
        hobby,
      </if>
    </trim>
    <trim prefix="values (" suffix=")" suffixOverrides=",">
      <if test="name != null">
        #{name,jdbcType=VARCHAR},
      </if>
      <if test="age != null">
        #{age,jdbcType=INTEGER},
      </if>
      <if test="hobby != null">
        #{hobby,jdbcType=VARCHAR},
      </if>
    </trim>
  </insert>
  • set标签,用于update,会根据传入参数的情况创建语句
  • foreach标签

<select id="findByIds" parameterType="com.southwind.entity.Account"
resultType="com.southwind.entity.Account">
 select * from account
 <where>
 <foreach collection="ids" open="id in (" close=")" item="id"
separator=",">
 #{id}
 </foreach>
 </where>
</select>

以上配置相当与

select * from account where id in (?,?,?)

Q.E.D.