一篇文章带你了解、学习、体验、回顾Mybatis

文章目录

    • 什么是Mybatis
    • 获取方式
    • 传统的JDBC
      • 传统jdbc的步骤
      • 传统jdbc带来的不便
    • 第一个Mybatis程序
      • 创建一个项目引入相关依赖
      • XML进行核心配置
      • 构建`SqlSessionFactory`
      • 创建实体类
      • 创建dao层接口和mapper
      • 将mapper.xml在核心配置文件中配置
      • 测试
    • CRUD
      • 条件查询
      • 插入
      • 修改
      • 删除
      • 模糊查询
    • &{}与#{}的应用
    • 核心文件配置
      • properties属性
      • typeAliases(类型别名)
      • Mapper映射配置
      • 日志打印配置
    • ResultMap结果集映射
      • 结果映射(resultMap)
      • 一对一查询
      • 一对多查询
      • 多对多查询
    • 动态SQL开发
      • IF查询
      • Choose、When、Otherwise
      • Set元素的使用
      • Foreach使用
    • 缓存
      • 本地缓存(一级缓存)
      • 二级缓存
    • 注解开发
    • Mybatis执行的基本流程
    • MyBatis解决了JDBC编程的问题

什么是Mybatis

  • MyBatis 是一款优秀的持久层框架
  • 它支持自定义 SQL、存储过程以及高级映射。
  • MyBatis 免除了几乎所有的 JDBC 代码以及设置参数和获取结果集的工作。
  • MyBatis 可以通过简单的 XML注解来配置和映射原始类型、接口和 Java POJO(Plain Old Java Objects,普通老式 Java 对象)为数据库中的记录。

获取方式

<dependency>
  <groupId>org.mybatisgroupId>
  <artifactId>mybatisartifactId>
  <version>x.x.xversion>
dependency>

传统的JDBC

传统jdbc的步骤

一篇文章带你了解、学习、体验、回顾Mybatis_第1张图片

package com.meimeixia.mybatis.jdbc;

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;

public class JdbcTest {

	//1. 加载驱动
	//2. 创建连接
	//3. 设置SQL语句
	//4. 创建一个Statement对象
	//5. 设置参数
	//6. 执行查询,得到一个ResultSet对象
	//7. 遍历结果集,输出结果
	//8. 释放资源
	public static void main(String[] args) {
		Connection connection = null;
		PreparedStatement preparedStatement = null;
		ResultSet resultSet = null;

		try {
			// 加载数据库驱动
			Class.forName("com.mysql.jdbc.Driver");

			// 通过驱动管理类获取数据库链接
			connection = DriverManager.getConnection("jdbc:mysql://localhost:3306/mybatis?characterEncoding=utf-8", "root", "liayun");
			// 定义sql语句 ?表示占位符
			String sql = "select * from user where username = ?";
			// 获取预处理statement
			preparedStatement = connection.prepareStatement(sql);
			// 设置参数,第一个参数为sql语句中参数的序号(从1开始),第二个参数为设置的参数值
			preparedStatement.setString(1, "王五");
			// 向数据库发出sql执行查询,查询出结果集
			resultSet = preparedStatement.executeQuery();
			// 遍历查询结果集
			while (resultSet.next()) {
				System.out.println(resultSet.getString("id")+" "+ resultSet.getString("username"));
			}
		} catch (Exception e) {
			e.printStackTrace();
		} finally {
			// 释放资源
			if (resultSet != null) {
				try {
					resultSet.close();
				} catch (SQLException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
			}
			if (preparedStatement != null) {
				try {
					preparedStatement.close();
				} catch (SQLException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
			}
			if (connection != null) {
				try {
					connection.close();
				} catch (SQLException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
			}
		}
	}

}

传统jdbc带来的不便

  • 数据库连接的创建、释放频繁造成资源浪费从而影响系统性能,如果使用数据库连接池可以解决这个问题。

  • 代码不易维护,失去了一旦变动,据需要修改java代码

    String sql = "select * from user where id = ?";
    
    preparedStatement  = connection.prepareStatement(sql);
    
  • 使用preparedStatement向占位符传参存在硬编码,因为SQL语句的where条件不确定,可能多也可能少,修改SQL语句还要修改Java代码,导致系统不易维护。

    preparedStatement.setString(1,"mike")
  • 对结果集解析存在硬编码(查询列名),SQL语句变化导致解析代码变化,系统不易维护,如果能将数据库记录封装成pojo对象解析则比较方便。

    // 遍历查询结果集
    while (resultSet.next()) {
        System.out.println(resultSet.getString("id")+" "+resultSet.getString("username"));
    }
    

第一个Mybatis程序

创建一个项目引入相关依赖

<dependencies>
    <dependency>
        <groupId>mysqlgroupId>
        <artifactId>mysql-connector-javaartifactId>
        <version>5.1.47version>
    dependency>

    <dependency>
        <groupId>org.mybatisgroupId>
        <artifactId>mybatisartifactId>
        <version>3.5.3version>
    dependency>

    <dependency>
        <groupId>junitgroupId>
        <artifactId>junitartifactId>
        <version>4.12version>
    dependency>
dependencies>

XML进行核心配置

XML 配置文件中包含了对 MyBatis 系统的核心设置,包括获取数据库连接实例的数据源(DataSource)以及决定事务作用域和控制方式的事务管理器(TransactionManager)。




<configuration>
    
    <properties resource="jdbc.properties"/>
    
    <typeAliases>
        
        
        <package name="domain"/>
    typeAliases>

    
    
    <environments default="mysql">
        
        <environment id="mysql">
            
            
            <transactionManager type="JDBC">transactionManager>
            
            <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="UserMapper.xml">mapper>
    mappers>

    
    
    
    
configuration>

jdbc.properties

driver=com.mysql.jdbc.Driver
url=jdbc:mysql://localhost:3306/mybatis
username=root
password=1996613nba

构建SqlSessionFactory

  1. 从 XML 文件中构建 SqlSessionFactory 的实例非常简单,建议使用类路径下的资源文件进行配置。 但也可以使用任意的输入流(InputStream)实例,比如用文件路径字符串或 file:// URL 构造的输入流。MyBatis 包含一个名叫 Resources 的工具类,它包含一些实用方法,使得从类路径或其它位置加载资源文件更加容易。
  2. 每个基于 MyBatis 的应用都是以一个 SqlSessionFactory的实例为核心的。SqlSessionFactory的实例可以通过SqlSessionFactoryBuilder获得。而 SqlSessionFactoryBuilder 则可以从 XML 配置文件或一个预先配置的 Configuration 实例来构建出 SqlSessionFactory 实例。
  3. 既然有了 SqlSessionFactory,顾名思义,我们可以从中获得 SqlSession 的实例。SqlSession 提供了在数据库执行 SQL 命令所需的所有方法。你可以通过 SqlSession 实例来直接执行已映射的 SQL 语句。

编写一个获取SqlSession对象的工具类以方便获取对象

package com.yy.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 YuanYong([email protected])
 * @date 2020/3/23  0:30
 * @description:TODO
 */
public class MybatisUtils {
    /**
     * 获取SqlSessionFactory对象
     */
    private static SqlSessionFactory sqlSessionFactory;
    static {
        String resource = "mybatis-comfig.xml";
        InputStream inputStream = null;
        try {
            inputStream = Resources.getResourceAsStream(resource);
        } catch (IOException e) {
            e.printStackTrace();
        }
        sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
    }

    /**
     * 返回SqlSession对象
     */
    public static SqlSession getSqlSession(){
        return sqlSessionFactory.openSession();
    }
}

创建实体类

使用Lombok

package com.yy.pojo;

import lombok.Data;

import java.util.Date;

/**
 * @author YuanYong(1218639030 @ qq.com)
 * @date 2020/3/23  0:51
 * @description:TODO
 */
@Data
public class User {
    private Integer id;
    private String username;
    private Date birthday;
    private String sex;
    private String address;
    
    private List<Order> orderList;
}

创建dao层接口和mapper

UserDao.java

package com.yy.mapper;

import com.yy.pojo.User;

import java.util.List;

/**
 * @author YuanYong(1218639030 @ qq.com)
 * @date 2020/3/23  1:00
 * @description:TODO
 */
public interface UserMapper {
    List<User> getUserList();
}

UserMapper.xml




<mapper namespace="com.yy.dao.UserMapper">
    
    <select id="getUserList" resultType="com.yy.pojo.User">
        select * from user where 1 = 1
    select>
mapper>
为了这个简单的例子,我们似乎写了不少配置,但其实并不多。在一个 XML 映射文件中,可以定义无数个映射语句,这样一来,XML 头部和文档类型声明部分就显得微不足道了。文档的其它部分很直白,容易理解。 

将mapper.xml在核心配置文件中配置


<mappers>
    <mapper resource="com/yy/dao/UserMapper.xml">mapper>
mappers>

测试

import com.yy.dao.UserDao;
import com.yy.pojo.User;
import com.yy.utils.MybatisUtils;
import org.apache.ibatis.session.SqlSession;
import org.junit.Test;

import java.util.List;

/**
 * @author YuanYong(1218639030 @ qq.com)
 * @date 2020/3/23  1:17
 * @description:TODO
 */
public class Mybatis {
    @Test
    public void test(){
        //1.获取sqlSession连接
        SqlSession session = MybatisUtils.getSqlSession();
        //2.获取mapper
        UserDao userDao = session.getMapper(UserDao.class);
        List<User> list = userDao.getUserList();
        //3.遍历结果集
        for (User user:list) {
            System.out.println(user);
        }
        //4.关闭连接
        session.close();
    }
}

测试结果

User(id=1, username=王五, birthday=null, sex=2, address=null)
User(id=10, username=张三, birthday=Thu Jul 10 00:00:00 CST 2014, sex=1, address=北京市)
User(id=16, username=张小明, birthday=null, sex=1, address=河南郑州)
User(id=22, username=陈小明, birthday=null, sex=1, address=河南郑州)
User(id=24, username=张三丰, birthday=null, sex=1, address=河南郑州)
User(id=25, username=陈小明, birthday=null, sex=1, address=河南郑州)
User(id=26, username=王五, birthday=null, sex=null, address=null)
User(id=29, username=苑勇, birthday=Sat Jan 18 00:00:00 CST 2020, sex=1, address=青岛)

CRUD

条件查询

  • 创建接口

    User selectUserById(Integer id);
    
  • mapper.xml文件中写入查询语句

    <select id="selectUserById" parameterType="Integer" resultType="com.yy.pojo.User">
        select * from user where id = #{id}
    select>
    
  • 测试

    @Test
    public void selectUserById(){
        //获取SqlSession连接
        SqlSession session = MybatisUtils.getSqlSession();
        //获取mapper对象
        UserMapper mapper = session.getMapper(UserMapper.class);
        //执行方法
        User user = mapper.selectUserById(1);
        //打印结果
        System.out.println(user);
        //关闭连接
        session.close();
    }
    
  • 结果

    User(id=1, username=王五, birthday=null, sex=2, address=null)
    

插入

  • 创建接口

    Integer insertUser(User user);
    
  • mapper.xml文件中写入查询语句

    <insert id="insertUser" parameterType="com.yy.pojo.User">
        <selectKey keyProperty="id" resultType="Integer" order="AFTER">
            SELECT LAST_INSERT_ID()
        selectKey>
        insert into 
        	user(username, birthday, sex, address) 
        values (#{username},#{birthday},#{sex},#{address});
    insert>
    
    属性 描述
    keyProperty selectKey 语句结果应该被设置到的目标属性。如果生成列不止一个,可以用逗号分隔多个属性名称。
    keyColumn 返回结果集中生成列属性的列名。如果生成列不止一个,可以用逗号分隔多个属性名称。
    resultType 结果的类型。通常 MyBatis 可以推断出来,但是为了更加准确,写上也不会有什么问题。MyBatis 允许将任何简单类型用作主键的类型,包括字符串。如果生成列不止一个,则可以使用包含期望属性的 Object 或 Map。
    order 可以设置为 BEFOREAFTER。如果设置为 BEFORE,那么它首先会生成主键,设置 keyProperty 再执行插入语句。如果设置为 AFTER,那么先执行插入语句,然后是 selectKey 中的语句 - 这和 Oracle 数据库的行为相似,在插入语句内部可能有嵌入索引调用。
    statementType 和前面一样,MyBatis 支持 STATEMENTPREPAREDCALLABLE 类型的映射语句,分别代表 Statement, PreparedStatementCallableStatement 类型。
  • 测试

    @Test
    public void insertUser(){
        User user = new User();
        user.setUsername("CodeYuan-Y");
        Date date = new Date();
        user.setBirthday(date);
        user.setSex("男");
        user.setAddress("青岛市");
    
        //获取SqlSession连接
        SqlSession session = MybatisUtils.getSqlSession();
        //获取mapper对象
        UserMapper mapper = session.getMapper(UserMapper.class);
        //执行sql
        mapper.insertUser(user);
        //提交事务
        session.commit();
        //输出自增id
        System.out.println("自增id为:"+user.getId());
        //关闭连接
        session.close();
    }
    
  • 结果

    自增id为:35
    

修改

  • 创建接口

    Integer updateUser(User user);
    
  • Sql语句

    <update id="updateUser" parameterType="com.yy.pojo.User">
        update user set username = #{username} where id = #{id}
    update>
    
  • 测试

    @Test
    public void updateUser(){
        User user = new User();
        user.setId(1);
        user.setUsername("CodeYuan-Y");
        //获取SqlSession对象
        SqlSession session = MybatisUtils.getSqlSession();
        //获取mapper对象
        UserMapper mapper = session.getMapper(UserMapper.class);
        //执行方法
        mapper.updateUser(user);
        //提交事务
        session.commit();
        //关闭连接
        session.close();
    }
    

删除

换一种写法…

假设我们的实体类,或者数据库中的表、字段过多,我们的参数应当考虑Map

  • 创建接口

    Integer deleteUser(Map<String,Object> map);
    
  • mapper.xml·中创建sql

    <delete id="deleteUser" parameterType="map">
        delete from user where id = #{userid} and username = #{username}
    delete>
    
  • 测试

    @Test
    public void deleteUser(){
        Map<String,Object> map = new HashMap<String, Object>();
        map.put("userid",29);
        map.put("username","苑勇");
        //获取SqlSession对象
        SqlSession session = MybatisUtils.getSqlSession();
        //获取mapper对象
        UserMapper mapper = session.getMapper(UserMapper.class);
        //调用方法
        mapper.deleteUser(map);
        //提交事务
        session.commit();
        //关闭连接
        session.close();
    }
    

模糊查询

创建接口

List<User> selectUserByLike(String s);
  • java代码中传递通配符。

    • mapper.xml配置

      <select id="selectUserByLike" parameterType="java.lang.String" resultType="com.yy.pojo.User">
          select * from user where username like #{value}
      select>
      
    • 测试

      @Test
      public void selectUserByLike(){
          //获取SqlSession对象
          SqlSession session = MybatisUtils.getSqlSession();
          //获取mapper
          UserMapper mapper = session.getMapper(UserMapper.class);
          //传入参数
          List<User> list = mapper.selectUserByLike("%张%");
          //提交事务
          session.commit();
          //遍历结果集
          for (User user:list) {
              System.out.println(user);
          }
          //关闭连接
      }
      
    • 结果

      User(id=10, username=张三, birthday=Thu Jul 10 00:00:00 CST 2014, sex=1, address=北京市)
      User(id=16, username=张小明, birthday=null, sex=1, address=河南郑州)
      User(id=24, username=张三丰, birthday=null, sex=1, address=河南郑州)
      
  • sql中拼接通配符(容易sql注入)

    • mapper.xml配置

      <select id="selectUserByLike" parameterType="java.lang.String" resultType="com.yy.pojo.User">
          select * from user where username like "%"#{value}"%"
      select>
      
    • 测试

      @Test
      public void selectUserByLike(){
          //获取SqlSession对象
          SqlSession session = MybatisUtils.getSqlSession();
          //获取mapper
          UserMapper mapper = session.getMapper(UserMapper.class);
          //传入参数
          List<User> list = mapper.selectUserByLike("张");
          //提交事务
          session.commit();
          //遍历结果集
          for (User user:list) {
              System.out.println(user);
          }
          //关闭连接
          session.close();
      }
      
    • 结果

      User(id=10, username=张三, birthday=Thu Jul 10 00:00:00 CST 2014, sex=1, address=北京市)
      User(id=16, username=张小明, birthday=null, sex=1, address=河南郑州)
      User(id=24, username=张三丰, birthday=null, sex=1, address=河南郑州)
      

&{}与#{}的应用

默认情况下,使用 #{} 参数语法时,MyBatis 会创建 PreparedStatement 参数占位符,并通过占位符安全地设置参数(就像使用 ? 一样)。 这样做更安全,更迅速,通常也是首选做法,不过有时你就是想直接在 SQL 语句中直接插入一个不转义的字符串。 比如 ORDER BY 子句,这时候你可以:

ORDER BY ${columnName}

这样,MyBatis 就不会修改或转义该字符串了。

核心文件配置

一篇文章带你了解、学习、体验、回顾Mybatis_第2张图片

properties属性

mybatis-config.xml文件中,可把数据库连接信息配置到properties标签当中。


<properties resource="jdbc.properties"/>



<dataSource type="POOLED">
    
    <property name="driver" value="${driver}"/>
    <property name="url" value="${url}"/>
    <property name="username" value="${username}"/>
    <property name="password" value="${password}"/>
dataSource>

MyBatis将按照下面的顺序来加载属性:

  1. 在properties元素体内定义的属性首先被读取;
  2. 然后会读取properties元素中resource或url加载的属性,它会覆盖已读取的同名属性。

说得通俗一点就是:先加载property元素内部的属性,然后再加载jdbc.properties文件外部的属性,如果有同名属性则会覆盖。可以将properties元素内部jdbc.username属性的值故意写错,即故意将数据库连接的用户名给整错,结果发现仍然好使,这足以说明问题了。

typeAliases(类型别名)

类型别名可为 Java 类型设置一个缩写名字。 它仅用于 XML 配置,意在降低冗余的全限定类名书 。

要注意的是:别名不区分大小写 UsEr 和 user效果是一样的

 
<typeAliases>
    <typeAlias type="com.yy.pojo.User" alias="user" />
typeAliases>

配置别名后,mapper里面的一些参数类型和返回类型就可以使用别名

原来的写法

<update id="updateUser" parameterType="com.yy.pojo.User">
    update user set username = #{username} where id = #{id}
update>

现在的写法

<update id="updateUser" parameterType="user">
    update user set username = #{username} where id = #{id}
update>

如果像这样为每一个pojo定义一个别名,那不是傻逼吗!万一一个项目里面有很多pojo类呢?所以我们可以批量定义别名,就像下面这样。

<typeAliases>
    <package name="com.yy.pojo"/>
typeAliases>

Mapper映射配置

Mapper(映射器)的配置有如下三种方式:

  1. 第一种方式:使用标签来加载相对于类路径的映射文件

  2. 第二种方式:使用标签配置映射文件的class扫描器,也就是说加载Mapper接口类路径下的映射文件。

    注意:这种方式要求Mapper接口名称和Mapper映射文件名称相同,且放在同一个目录中。

  3. 第三种方式:使用标签配置映射文件包扫描,注册指定包下的所有Mapper接口,也就是说能加载指定包下的所有映射文件了。

    注意:这种方式也要求Mapper接口名称和Mapper映射文件名称相同,且放在同一个目录中。

虽然Mapper(映射器)配置有以上三种方式,但是在实际开发中就用第三种方法,其他方式仅做了解就行!

第三种实例


<mappers>
    
    
    <package name="com.yy.mybatis.mapper"/>
mappers>

第三种方式IDEA maven项目默认不会把src下除java文件外的文件打包到classes文件夹下,需要在pom.xml中增加配置

src/main/java **/*.xml

日志打印配置

配置日志打印可以更好的帮助我们查看过程,发现错误,以下是STDOUT_LOGGING配置

<settings>
    <setting name="logImpl" value="STDOUT_LOGGING"/>
settings>

输出日志实例

Created connection 33105141.
Setting autocommit to false on JDBC Connection [com.mysql.jdbc.JDBC4Connection@1f924f5]
==>  Preparing: select * from user where username like "%"?"%" 
==> Parameters: 张(String)
<==    Columns: id, username, birthday, sex, address
<==        Row: 10, 张三, 2014-07-10, 1, 北京市
<==        Row: 16, 张小明, null, 1, 河南郑州
<==        Row: 24, 张三丰, null, 1, 河南郑州
<==      Total: 3

ResultMap结果集映射

结果映射(resultMap)

resultType虽然可以指定将查询结果映射为pojo,但需要pojo的属性名和SQL语句查询的列名一致方可映射成功。如果SQL语句查询字段名和pojo的属性名不一致,那么可以通过resultMap将字段名和属性名作一个对应关系,resultMap实质上还是需要将查询结果映射到pojo对象中。resultMap可以实现将查询结果映射为复杂类型的pojo,比如在查询结果映射对象中包括pojo和List集合就能分别实现一对一查询和一对多查询。

  • 创建接口

    List<User> getUserListMap();
    
  • 配置映射

    
    <resultMap id="user_list_map" type="user">
        
        <id property="id" column="id"/>
        
        <result property="username" column="username"/>
        <result property="sex" column="sex"/>
        <result property="address" column="address"/>
    resultMap>
    
    <select id="getUserListMap" resultMap="user_list_map">
        select
        id,username,sex,address
        from user
    select>
    
  • 测试

    @Test
    public void getUserListMap(){
        //获取SqlSession对象
        SqlSession session = MybatisUtils.getSqlSession();
        //获取mapper
        UserMapper mapper = session.getMapper(UserMapper.class);
        //调用方法
        List<User> userListMap = mapper.getUserListMap();
        //提交事务
        session.commit();
        //输出结果集
        for (User u:userListMap) {
            System.out.println(u);
        }
        //关闭连接
        session.close();
    }
    
  • 结果

    User(id=1, username=CodeYuan-Y, birthday=null, sex=2, address=null)
    User(id=10, username=张三, birthday=null, sex=1, address=北京市)
    User(id=16, username=张小明, birthday=null, sex=1, address=河南郑州)
    User(id=22, username=陈小明, birthday=null, sex=1, address=河南郑州)
    User(id=24, username=张三丰, birthday=null, sex=1, address=河南郑州)
    User(id=25, username=陈小明, birthday=null, sex=1, address=河南郑州)
    User(id=26, username=王五, birthday=null, sex=null, address=null)
    User(id=35, username=CodeYuan-Y, birthday=null, sex=男, address=青岛市)
    
  • constructor

    - 用于在实例化类时,注入结果到构造方法中

    • idArg - ID 参数;标记出作为 ID 的结果可以帮助提高整体性能
    • arg - 将被注入到构造方法的一个普通结果
  • id – 一个 ID 结果;标记出作为 ID 的结果可以帮助提高整体性能

  • result – 注入到字段或 JavaBean 属性的普通结果

  • association

    一个复杂类型的关联;许多结果将包装成这种类型

    • 嵌套结果映射 – 关联可以是 resultMap 元素,或是对其它结果映射的引用
  • collection

    一个复杂类型的集合

    • 嵌套结果映射 – 集合可以是 resultMap 元素,或是对其它结果映射的引用
属性 描述
property 映射到列结果的字段或属性。如果 JavaBean 有这个名字的属性(property),会先使用该属性。否则 MyBatis 将会寻找给定名称的字段(field)。 无论是哪一种情形,你都可以使用常见的点式分隔形式进行复杂属性导航。 比如,你可以这样映射一些简单的东西:“username”,或者映射到一些复杂的东西上:“address.street.number”。
column 数据库中的列名,或者是列的别名。一般情况下,这和传递给 resultSet.getString(columnName) 方法的参数一样。
javaType 一个 Java 类的全限定名,或一个类型别名(关于内置的类型别名,可以参考上面的表格)。 如果你映射到一个 JavaBean,MyBatis 通常可以推断类型。然而,如果你映射到的是 HashMap,那么你应该明确地指定 javaType 来保证行为与期望的相一致。

一对一查询

场景:一个订单中 订单信息 + User信息

  • 创建订单实体类

    @Data
    public class Order {
        private Integer id;
        private String number;
        private Date createtime;
        private String note;
        private User user;
    }
    
  • 创建接口

    public interface OrderMapper {
        Order selectOrderById(Integer id);
    }
    
  • mapper.xml配置

    <resultMap id="orderMap" type="order">
        <id property="id" column="id"/>
        <result property="number" column="number"/>
        <result property="createtime" column="createtime" />
        <result property="note" column="note"/>
        
        
        <association property="user" javaType="user">
            <id property="id" column="id"/>
            <result property="username" column="username"/>
            <result property="sex" column="sex"/>
            <result property="address" column="address"/>
        association>
    resultMap>
    <select id="selectOrderById" parameterType="int" resultMap="orderMap">
        select
        o.id,o.number,o.createtime,o.note,
        u.id,u.username,u.sex,u.address
        from mybatis.order as o join user as u on user_id = u.id
        where o.id = #{id}
    select>
    
  • 测试

    @Test
    public void selectOrderById(){
        //获取SqlSession对象
        SqlSession session = MybatisUtils.getSqlSession();
        //获取mapper
        OrderMapper mapper = session.getMapper(OrderMapper.class);
        //调用方法
        Order order = mapper.selectOrderById(3);
        //提交事务
        session.commit();
        //输出结果集
        System.out.println(order);
        //关闭连接
        session.close();
    }
    
  • 结果

    <==    Columns: id, number, createtime, note, id, username, sex, address
    <==        Row: 3, 1000010, 2015-02-04 13:22:35.0, null, 1, CodeYuan-Y, 2, null
    <==      Total:
    

一对多查询

场景 查询一个用户所有的订单信息

  • 接口

    List<User> selectOrderByUserId(Integer id);
    
  • mapper.xml配置

    <resultMap id="userMap" type="user">
        <id property="id" column="id"/>
        <result property="username" column="username"/>
        <result property="sex" column="sex"/>
        <result property="address" column="address"/>
        
        <collection property="orderList" javaType="list" ofType="order">
            <id property="id" column="id"/>
            <result property="number" column="number"/>
            <result property="note" column="note"/>
        collection>
    resultMap>
    
    <select id="selectOrderByUserId" parameterType="Integer" resultMap="userMap">
        select
        u.id,u.username,u.sex,u.address,
        o.id,o.number,o.note
        from mybatis.order as o,user as u
        where o.user_id = u.id and u.id = #{id}
    select>
    
  • 测试

    @Test
    public void selectOrderByUserId(){
        //获取SqlSession对象
        SqlSession session = MybatisUtils.getSqlSession();
        //获取mapper
        OrderMapper mapper = session.getMapper(OrderMapper.class);
        //调用方法
        List<User> list = mapper.selectOrderByUserId(1);
        //提交事务
        session.commit();
        //输出结果集
        System.out.println(list);
        //关闭连接
        session.close();
    }
    
  • 结果

    ==>  Preparing: select u.id,u.username,u.sex,u.address, o.id,o.number,o.note from mybatis.order as o,user as u where o.user_id = u.id and u.id = ? 
    ==> Parameters: 1(Integer)
    <==    Columns: id, username, sex, address, id, number, note
    <==        Row: 1, CodeYuan-Y, 2, null, 3, 1000010, 铅笔
    <==        Row: 1, CodeYuan-Y, 2, null, 4, 1000011, 钢笔
    <==      Total: 2
    

多对多查询

多对多、多对多对多…只要你想对下去一定可以,它的实现原理主要是collection的嵌套又嵌套

动态SQL开发

​ 动态 SQL 是 MyBatis 的强大特性之一。如果你使用过 JDBC 或其它类似的框架,你应该能理解根据不同条件拼接 SQL 语句有多痛苦,例如拼接时要确保不能忘记添加必要的空格,还要注意去掉列表最后一个列名的逗号。利用动态 SQL,可以彻底摆脱这种痛苦。

​ 使用动态 SQL 并非一件易事,但借助可用于任何 SQL 映射语句中的强大的动态 SQL 语言,MyBatis 显著地提升了这一特性的易用性。

​ 如果你之前用过 JSTL 或任何基于类 XML 语言的文本处理器,你对动态 SQL 元素可能会感觉似曾相识。在 MyBatis 之前的版本中,需要花时间了解大量的元素。借助功能强大的基于 OGNL 的表达式,MyBatis 3 替换了之前的大部分元素,大大精简了元素种类,现在要学习的元素种类比原来的一半还要少。

IF查询

  • 场景:查询时,如果输入了某些内容就模糊查询,如果没输入就查询全部

  • 接口

    List<User> selectUserList(String str);
    
  • sql语句

    <select id="selectUserList" parameterType="string" resultType="user">
        select * from user where 1 = 1
        <if test="username != null">
            and username like #{username}
        if>
    select>
    ------------------或------------------
    <select id="selectUserList" parameterType="string" resultType="user">
        select * from user
        <where>
            <if test="username != null">
                and username like #{username}
            if>
        where>
    select>
    
  • 测试

    @Test
    public void selectUserList(){
        //获取SqlSession对象
        SqlSession session = MybatisUtils.getSqlSession();
        //获取mapper
        UserMapper mapper = session.getMapper(UserMapper.class);
        //调用方法
        List<User> list = mapper.selectUserList("%张%");
        //提交事务
        session.commit();
        //输出结果集
        System.out.println(list);
        //关闭连接
        session.close();
    }
    
  • 结果

    ==>  Preparing: select * from user where 1 = 1 and username like ? 
    ==> Parameters: %张%(String)
    <==    Columns: id, username, birthday, sex, address
    <==        Row: 10, 张三, 2014-07-10, 1, 北京市
    <==        Row: 16, 张小明, null, 1, 河南郑州
    <==        Row: 24, 张三丰, null, 1, 河南郑州
    <==      Total: 3
    

Choose、When、Otherwise

  • 接口

    List<User> selectUserList(User user);
    
  • sql语句

    <select id="selectUserList" parameterType="user" resultType="user">
        select * from user
        <where>
            <choose>
                <when test="username != null">
                    username like #{username}
                when>
                <when test="sex != null">
                    sex = '男'
                when>
                <otherwise>
                    id = 1
                otherwise>
            choose>
        where>
    select>
    

    when - when - otherwise 的结构 相当于 if - else if - else

  • 测试

    @Test
    public void selectUserList(){
        User user = new User();
        //获取SqlSession对象
        SqlSession session = MybatisUtils.getSqlSession();
        //获取mapper
        UserMapper mapper = session.getMapper(UserMapper.class);
        //调用方法
        List<User> list = mapper.selectUserList(user);
        //提交事务
        session.commit();
        //输出结果集
        System.out.println(list);
        //关闭连接
        session.close();
    }
    
  • 结果

    ==>  Preparing: select * from user WHERE id = 1 
    ==> Parameters: 
    <==    Columns: id, username, birthday, sex, address
    <==        Row: 1, CodeYuan-Y, null, 2, null
    <==      Total: 1
    

Set元素的使用

set 元素可以用于动态包含需要更新的列,忽略其它不更新的列。比如:

  • 接口

    Integer updateUserUseSet(User user);
    
  • sql语句

    <update id="updateUserUseSet" parameterType="user">
        update user
        <set>
            <if test="username != null">username = #{username},if>
            <if test="sex != null">sex = #{sex},if>
            address = #{address}
        set>
        where id = #{id}
    update>
    
  • 测试

    @Test
    public void updateUserUseSet(){
        User user = new User();
        user.setId(1);
        user.setSex("男");
        user.setAddress("山东青岛");
        //获取SqlSession对象
        SqlSession session = MybatisUtils.getSqlSession();
        //获取mapper
        UserMapper mapper = session.getMapper(UserMapper.class);
        //调用方法
        mapper.updateUserUseSet(user);
        //提交事务
        session.commit();
        //关闭连接
        session.close();
    }
    
  • 结果

    ==>  Preparing: update user SET sex = ?, address = ? where id = ? 
    ==> Parameters: 男(String), 山东青岛(String), 1(Integer)
    <==    Updates: 1
    

Foreach使用

  • **item:**集合中元素迭代时的别名,该参数为必选。

  • index:在list和数组中,index是元素的序号,在map中,index是元素的key,该参数可选

  • open:foreach代码的开始符号,一般是(和close=")"合用。常用在in(),values()时。该参数可选

  • separator:元素之间的分隔符,例如在in()的时候,separator=","会自动在元素中间用“,“隔开,避免手动输入逗号导致sql错误,如in(1,2,)这样。该参数可选。

  • close: select * from user where id in #{item} foreach代码的关闭符号,一般是)和open="("合用。常用在in(),values()时。该参数可选。

  • collection: 要做foreach的对象,作为入参时,List对象默认用"list"代替作为键,数组对象有"array"代替作为键,Map对象没有默认的键。当然在作为入参时可以使用@Param(“keyName”)来设置键,设置keyName后,list,array将会失效。 除了入参这种情况外,还有一种作为参数对象的某个字段的时候。举个例子:如果User有属性List ids。入参是User对象,那么这个collection = “ids”.如果User有属性Ids ids;其中Ids是个对象,Ids有个属性List id;入参是User对象,那么collection = "ids.id"

  • 接口

    List<User> selectForeachById(List<Integer> list);
    
  • sql语句

    <select id="selectForeachById" parameterType="list" resultType="user">
        select * from user where id in
        <foreach collection="list" item="item" open=" (" separator="," close=")">
            #{item}
        foreach>
    select>
    
  • 测试

    @Test
    public void selectForeachById(){
        List<Integer> num = new ArrayList<Integer>();
        num.add(1);
        num.add(10);
        num.add(16);
        //获取SqlSession对象
        SqlSession session = MybatisUtils.getSqlSession();
        //获取mapper
        UserMapper mapper = session.getMapper(UserMapper.class);
        //调用方法
        List<User> list = mapper.selectForeachById(num);
        //输出结果集
        for (User u : list) {
            System.out.println(u);
        }
        //关闭连接
        session.close();
    }
    
  • 结果

    ==>  Preparing: select * from user where id in ( ? , ? , ? ) 
    ==> Parameters: 1(Integer), 10(Integer), 16(Integer)
    <==    Columns: id, username, birthday, sex, address
    <==        Row: 1, CodeYuan-Y, null, 男, 山东青岛
    <==        Row: 10, 张三, 2014-07-10, 1, 北京市
    <==        Row: 16, 张小明, null, 1, 河南郑州
    <==      Total: 3
    

缓存

本地缓存(一级缓存)

Mybatis 使用到了两种缓存:本地缓存(local cache)和二级缓存(second level cache)。

每当一个新 session 被创建,MyBatis 就会创建一个与之相关联的本地缓存。任何在 session 执行过的查询结果都会被保存在本地缓存中,所以,当再次执行参数相同的相同查询时,就不需要实际查询数据库了。本地缓存将会在做出修改、事务提交或回滚,以及关闭 session 时清空。

默认情况下,本地缓存数据的生命周期等同于整个 session 的周期。由于缓存会被用来解决循环引用问题和加快重复嵌套查询的速度,所以无法将其完全禁用。但是你可以通过设置 localCacheScope=STATEMENT 来只在语句执行时使用缓存。

你可以随时调用以下方法来清空本地缓存:

void clearCache()

确保 SqlSession 被关闭

void close()

以上面代码为例:

@Test
public void selectForeachById(){
    List<Integer> num = new ArrayList<Integer>();
    num.add(1);
    num.add(10);
    num.add(16);
    //获取SqlSession对象
    SqlSession session = MybatisUtils.getSqlSession();
    //获取mapper
    UserMapper mapper = session.getMapper(UserMapper.class);
    //调用方法
    List<User> list = mapper.selectForeachById(num);
    System.out.println("======================================");
    List<User> lis = mapper.selectForeachById(num);

    //比较
    System.out.println(lis.equals(list));
    //关闭连接
    session.close();
}

​ 我们在调用一个方法查询一段内容后,再次调用查看日志发现,系统只进行了一次sql查询,那么另一次调用的方法干什么了呢?它是对缓存进行了查询,而且查询的内容相同。

==>  Preparing: select * from user where id in ( ? , ? , ? ) 
==> Parameters: 1(Integer), 10(Integer), 16(Integer)
<==    Columns: id, username, birthday, sex, address
<==        Row: 1, CodeYuan-Y, null, 男, 山东青岛
<==        Row: 10, 张三, 2014-07-10, 1, 北京市
<==        Row: 16, 张小明, null, 1, 河南郑州
<==      Total: 3
======================================
true

当我们在调用第一次查询方法后,清除缓存,然后再次查询会发现。

@Test
public void selectForeachById(){
    List<Integer> num = new ArrayList<Integer>();
    num.add(1);
    num.add(10);
    num.add(16);
    //获取SqlSession对象
    SqlSession session = MybatisUtils.getSqlSession();
    //获取mapper
    UserMapper mapper = session.getMapper(UserMapper.class);
    //调用方法
    List<User> list = mapper.selectForeachById(num);
    session.clearCache();
    System.out.println("===================清除缓存后===================");
    List<User> lis = mapper.selectForeachById(num);

    //比较
    System.out.println(lis.equals(list));
    //关闭连接
    session.close();
}
==>  Preparing: select * from user where id in ( ? , ? , ? ) 
==> Parameters: 1(Integer), 10(Integer), 16(Integer)
<==    Columns: id, username, birthday, sex, address
<==        Row: 1, CodeYuan-Y, null, 男, 山东青岛
<==        Row: 10, 张三, 2014-07-10, 1, 北京市
<==        Row: 16, 张小明, null, 1, 河南郑州
<==      Total: 3
===================清除缓存后===================
==>  Preparing: select * from user where id in ( ? , ? , ? ) 
==> Parameters: 1(Integer), 10(Integer), 16(Integer)
<==    Columns: id, username, birthday, sex, address
<==        Row: 1, CodeYuan-Y, null, 男, 山东青岛
<==        Row: 10, 张三, 2014-07-10, 1, 北京市
<==        Row: 16, 张小明, null, 1, 河南郑州
<==      Total: 3
true

由于缓存被清除了,所有系统进行了两次查询。

  • 同一个 SqlSession 中, Mybatis 会把执行的方法和参数通过算法生成缓存的键值, 将键值和结果存放在一个 Map 中, 如果后续的键值一样, 则直接从 Map 中获取数据;

  • 不同的 SqlSession 之间的缓存是相互隔离的

    一级缓存只在同一个SqlSession使用范围内有效

  • 用一个 SqlSession, 可以通过配置使得在查询前清空缓存;

  • 任何的 UPDATE, INSERT, DELETE 语句都会清空缓存。

    由于内容发生了变化,缓存的内容与数据库中的不相符,所以没有存在的意义了

二级缓存

​ 在...mapper.xml加以下标签,需要注意的是Mybatis使用二级缓存,实体类必须进行序列化。至于为什么要序列化?是由于缓存的key-value的存储特性。

<cache/>

测试

@Test
public void selectForeachById(){
    List<Integer> num = new ArrayList<Integer>();
    num.add(1);
    num.add(10);
    num.add(16);
    //获取SqlSession对象
    SqlSession session= MybatisUtils.getSqlSession();
    //获取mapper
    UserMapper mapper1 = session.getMapper(UserMapper.class);
    //调用方法
    List<User> list = mapper1.selectForeachById(num);
    //关闭连接
    session.close();

    System.out.println("===================重启SqlSession===================");

    //获取SqlSession对象
    session = MybatisUtils.getSqlSession();
    //获取mapper
    UserMapper mapper2 = session.getMapper(UserMapper.class);
    //调用方法
    List<User> lis = mapper2.selectForeachById(num);
    //关闭连接
    session.close();

}

结果

Setting autocommit to false on JDBC Connection [com.mysql.jdbc.JDBC4Connection@c7b4de]
==>  Preparing: select * from user where id in ( ? , ? , ? ) 
==> Parameters: 1(Integer), 10(Integer), 16(Integer)
<==    Columns: id, username, birthday, sex, address
<==        Row: 1, CodeYuan-Y, null, 男, 山东青岛
<==        Row: 10, 张三, 2014-07-10, 1, 北京市
<==        Row: 16, 张小明, null, 1, 河南郑州
<==      Total: 3
Resetting autocommit to true on JDBC Connection [com.mysql.jdbc.JDBC4Connection@c7b4de]
Closing JDBC Connection [com.mysql.jdbc.JDBC4Connection@c7b4de]
Returned connection 13087966 to pool.
===================重启SqlSession===================
Cache Hit Ratio [com.yy.mapper.UserMapper]: 0.5

基本上就是这样。这个简单语句的效果如下:

  • 映射语句文件中的所有 select 语句的结果将会被缓存。
  • 映射语句文件中的所有 insert、update 和 delete 语句会刷新缓存。
  • 缓存会使用最近最少使用算法(LRU, Least Recently Used)算法来清除不需要的缓存。
  • 缓存不会定时进行刷新(也就是说,没有刷新间隔)。

提示 缓存只作用于 cache 标签所在的映射文件中的语句。如果你混合使用 Java API 和 XML 映射文件,在共用接口中的语句将不会被默认缓存。你需要使用 @CacheNamespaceRef 注解指定缓存作用域。

这些属性可以通过 cache 元素的属性来修改。比如:


这个更高级的配置创建了一个 FIFO 缓存,每隔 60 秒刷新,最多可以存储结果对象或列表的 512 个引用,而且返回的对象被认为是只读的,因此对它们进行修改可能会在不同线程中的调用者产生冲突。

可用的清除策略有:

  • LRU – 最近最少使用:移除最长时间不被使用的对象。
  • FIFO – 先进先出:按对象进入缓存的顺序来移除它们。
  • SOFT – 软引用:基于垃圾回收器状态和软引用规则移除对象。
  • WEAK – 弱引用:更积极地基于垃圾收集器状态和弱引用规则移除对象。

默认的清除策略是 LRU。

flushInterval(刷新间隔)属性可以被设置为任意的正整数,设置的值应该是一个以毫秒为单位的合理时间量。 默认情况是不设置,也就是没有刷新间隔,缓存仅仅会在调用语句时刷新。

size(引用数目)属性可以被设置为任意正整数,要注意欲缓存对象的大小和运行环境中可用的内存资源。默认值是 1024。

readOnly(只读)属性可以被设置为 true 或 false。只读的缓存会给所有调用者返回缓存对象的相同实例。 因此这些对象不能被修改。这就提供了可观的性能提升。而可读写的缓存会(通过序列化)返回缓存对象的拷贝。 速度上会慢一些,但是更安全,因此默认值是 false。

提示 二级缓存是事务性的。这意味着,当 SqlSession 完成并提交时,或是完成并回滚,但没有执行 flushCache=true 的 insert/delete/update 语句时,缓存会获得更新

注解开发

  • 配置映射文件

    
    <mappers>
        <mapper class="com.yy.mapper.UserMapper" />
    mappers>
    
  • 注解

    @Select("select * from user where 1 = 1")
    List<User> getUserList();
    

更多注解使用方法

使用注解来映射简单语句会使代码显得更加简洁,但对于稍微复杂一点的语句,Java 注解不仅力不从心,还会让你本就复杂的 SQL 语句更加混乱不堪。 因此,如果你需要做一些很复杂的操作,最好用 XML 来映射语句。

选择何种方式来配置映射,以及认为是否应该要统一映射语句定义的形式,完全取决于你和你的团队。 换句话说,永远不要拘泥于一种方式,你可以很轻松的在基于注解和 XML 的语句映射方式间自由移植和切换。

Mybatis执行的基本流程

一篇文章带你了解、学习、体验、回顾Mybatis_第3张图片

  • 第一步通过SqlSessionFactoryBuilder创建SqlSessionFactory
  • 第二步通过SqlSessionFactory创建SqlSession
  • 第三步通过SqlSession拿到Mapper对象的代理。
  • 第四步通过MapperProxy调用Maper中相应的方法。
(1)读取MyBatis的配置文件。mybatis-config.xml为MyBatis的全局配置文件,用于配置数据库连接信息。

(2)加载映射文件。映射文件即SQL映射文件,该文件中配置了操作数据库的SQL语句,需要在MyBatis配置文件mybatis-config.xml中加载。mybatis-config.xml 文件可以加载多个映射文件,每个文件对应数据库中的一张表。

(3)构造会话工厂。通过MyBatis的环境配置信息构建会话工厂SqlSessionFactory。

(4)创建会话对象。由会话工厂创建SqlSession对象,该对象中包含了执行SQL语句的所有方法。

(5)Executor执行器。MyBatis底层定义了一个Executor接口来操作数据库,它将根据SqlSession传递的参数动态地生成需要执行的SQL语句,同时负责查询缓存的维护。

(6)MappedStatement对象。在Executor接口的执行方法中有一个MappedStatement类型的参数,该参数是对映射信息的封装,用于存储要映射的SQL语句的id、参数等信息。

(7)输入参数映射。输入参数类型可以是Map、List等集合类型,也可以是基本数据类型和POJO类型。输入参数映射过程类似于JDBC对preparedStatement对象设置参数的过程。

(8)输出结果映射。输出结果类型可以是Map、List等集合类型,也可以是基本数据类型和POJO类型。输出结果映射过程类似于JDBC对结果集的解析过程。

MyBatis解决了JDBC编程的问题

  1. 数据库连接创建、释放频繁造成系统资源浪费从而影响系统性能,如果使用数据库连接池可解决此问题。
    如何解决这个问题呢? 可以在SqlMapConfig.xml文件中配置数据库连接池,使用连接池管理数据库连接。
  2. SQL语句写在代码中造成代码不易维护,实际应用中SQL语句变化的可能较大,如果SQL语句一旦变动,那么就需要改变Java代码了。
    如何解决这个问题呢? 可以将SQL语句配置在映射文件中与Java代码分离。
  3. 向SQL语句传参数麻烦,因为SQL语句的where条件不一定,可能多也可能少,修改SQL语句还要修改Java代码,导致系统不易维护。
    如何解决这个问题呢? MyBatis可以自动将Java对象映射至SQL语句中,通过Statement中的parameterType定义输入参数的类型。
  4. 对结果集解析麻烦,SQL变化导致解析代码变化,且解析前需要遍历,如果能将数据库记录封装成pojo对象解析则比较方便。
    如何解决这个问题呢? MyBatis可以自动将SQL语句的执行结果映射至Java对象,通过Statement中的resultType定义输出结果的类型。

你可能感兴趣的:(java框架,java)