MyBatis入门详细解析

5. mybatis

5.1 搭建mybatis

5.1.1 开发环境

mysql 8的驱动类使用:com.mysql.cj.jdbc.Driver

mysql 8的url使用:jdbc:mysql://localhost:3306/数据库名?serverTimezone=UTC

5.1.2 创建maven工程

创建空项目 ----> New一个maven模板 -----> 在设置中重写maven的本地仓库地址和setting.xml

然后在pom.xml文件中将打包方式设置为jar

<packaging>jar</packaging>

引入依赖

 <dependencies>


        <!-- mybatis核心-->
        <dependency>
            <groupId>org.mybatis</groupId>
            <artifactId>mybatis</artifactId>
            <version>3.5.9</version>
        </dependency>

        <!--junit测试-->
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.12</version>
            <scope>test</scope>
        </dependency>

        <!--mysql驱动-->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>8.0.29</version>
        </dependency>


</dependencies>
5.1.3 创建mybatis的核心配置文件

在resource下创建一个xml文件,名字随意,这里取 mybatis-config.xml。在里面添加下面的代码(从mybatis官网复制并修改相应的属性值)

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
        PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
        "https://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>

    <!--配置连接数据库环境-->
    <environments default="development">
        <environment id="development">
            <transactionManager type="JDBC"/>
            <dataSource type="POOLED">
                
                
                <property name="driver" value="com.mysql.cj.jdbc.Driver"/>
                <property name="url" value="jdbc:mysql://localhost:3306/mybatis?serverTimezone=UTC"/>
                <property name="username" value="root"/>
                <property name="password" value="123456"/>
                
                
            </dataSource>
        </environment>
    </environments>

    <!--引入mybatis的映射文件-->
    <mappers>
        <mapper resource=""/>
    </mappers>
</configuration>
5.1.4 创建mapper接口

mybatis中的mapper相当于dao

package com.spark.mapper;

public interface UserMapper {
    int insertUser();
}

5.1.5 创建mybatis映射文件

ORM:对象关系映射

类 -> 表

属性 -> 字段

对象 -> 行

  1. 映射命名规则:表对应实体类类名+Mapper.xml

    比如:表t_user,映射的实体类为User,对应的映射文件为UserMapper.xml

    因此一个映射文件对应一张表,一个实体类

    映射文件用于编写sql,操作数据

    文件存放在resource/mappers目录下

  2. 在映射文件中

    • mapper接口的全类名要和映射文件的namespace一致

      <mapper namespace="com.spark.mapper.UserMapper">
      </mapper>
      
    • mapper接口中的方法名要和映射文件中的sql的id保持一致

          <!--int insertUser();-->
          <insert id="insertUser">
              insert into user values (null,'hutao',23,0);
          </insert>
      
    • 然后在之前创建的mybatis-config.xml文件中的mapper标签中引入映射文件

          <!--引入mybatis的映射文件-->
          <mappers>
              <mapper resource="mappers/UserMapper.xml"/>
          </mappers>
      
5.1.6 用mybatis实现在数据库中添加用户信息

我们在Test目录下的Java文件夹中创建测试类

package com.spark;

import com.spark.mapper.UserMapper;
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;

public class MyBatisTest {
    @Test
    public void testInsert() throws IOException {
        //获取核心配置文件的输入流
        InputStream is = Resources.getResourceAsStream("mybatis-config.xml");
        //获取sqlSessionFactoryBuilder对象
        SqlSessionFactoryBuilder sqlSessionFactoryBuilder = new SqlSessionFactoryBuilder();
        //获取SqlSessionFactory对象
        SqlSessionFactory sqlSessionFactory = sqlSessionFactoryBuilder.build(is);
        //获取sql的会话对象SqlSession,是MyBatis操作数据库的对象
        SqlSession sqlSession = sqlSessionFactory.openSession();
        
        
        
        
        //获取UserMapper的代理实现类对象(实现类需要创建对象,用代理对象就不需要我们另外创建了。)
        //(同时,在UserMapper.xml文件中我们已经重写了UserMapper接口的方法,所以可以直接用代理对象调用。这就是映射的作用)
        UserMapper mapper = sqlSession.getMapper(UserMapper.class);
        //调用Mapper接口中的方法,实现添加用户信息的功能
        int result = mapper.insertUser();
        System.out.println("结果:"+result);
        //提交事务
        sqlSession.commit();//不添加的话,事务会被回滚,但是可以在控制台看到结果:1
        //关闭会话对象
        sqlSession.close();

    }
5.1.7 mybatis优化

第一处优化:

对于下面的代码:

  //获取核心配置文件的输入流
        InputStream is = Resources.getResourceAsStream("mybatis-config.xml");
        //获取sqlSessionFactoryBuilder对象
        SqlSessionFactoryBuilder sqlSessionFactoryBuilder = new SqlSessionFactoryBuilder();
        //获取SqlSessionFactory对象
        SqlSessionFactory sqlSessionFactory = sqlSessionFactoryBuilder.build(is);
        //获取sql的会话对象SqlSession,是MyBatis操作数据库的对象
        SqlSession sqlSession = sqlSessionFactory.openSession();

由于这是一种固定的代码,我们可以创建一个工具类,将上述代码在工具类中定义成一个方法,以后要使用直接调用即可

第二处优化(其实也不算优化)

我们可以不用写:

        UserMapper mapper = sqlSession.getMapper(UserMapper.class);
        //调用Mapper接口中的方法,实现添加用户信息的功能
        int result = mapper.insertUser();
        System.out.println("结果:"+result);

直接利用sqlSession中的方法:

//通过找到唯一标识找到sql并执行,唯一标识是namesapce.sqlId       
int res = sqlSession.insert("com.spark.mapper.UserMapper.insertUser");
       
System.out.println(res);

第三处优化

我们最后手动写了一个事务提交

 sqlSession.commit();

如果想要自动提交的话,可以在开启会话的时候就设置

//这个openSession()是个有参方法,如果加入true,就会改成自动提交
SqlSession sqlSession = sqlSessionFactory.openSession();
SqlSession sqlSession = sqlSessionFactory.openSession(true);

第四处优化

可以添加日志功mybatis-config.xml文件中引入依赖)

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

这个依赖要写在configration标签中的,且放在最上方

5.1.8 日志级别

fatal > error > warn > info(信息) > debug

从左往右,打印的日志内容越详细

5.1.9 mybatis实现增删改查

对于增删改比较简单。

首先在UserMapper接口中定义方法,没有返回值。而查询数据需要返回值,单条查询返回User类型,多条查询返回以User为泛型的集合类型

package com.spark.mapper;

import com.spark.pojo.User;

import java.util.List;

public interface UserMapper {
    /**
     * 添加用户信息
     * @return
     */
    int insertUser();


    /**
     * 修改用户信息
     * @return
     */
    void updateUser();


    /**
     * 删除用户信息
     */
    void deleteUser();


    /**
     * 根据id查询用户
     * @return
     */
    User getUserById();


    /**
     * 查询所有用户
     * @return
     */
    List<User> getAllUser();
}

然后在UserMapper.xml中编写sql语句。查询操作要多写一步。

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "https://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.spark.mapper.UserMapper">

    <!--int insertUser();-->
    <insert id="insertUser">
        insert into user values (null,'hutao',23,0);
    </insert>



    <!--void updateUser();-->
    <update id="updateUser">
        update user set name = 'qiqi' where id = 3;
    </update>

    <!--void deleteUser();-->
    <delete id="deleteUser">
        delete from user where id = 3;
    </delete>

    <!--User getUserById();-->
    <!--
        resultType:设置结果类型,即查询的数据要转换为java类型
        resultMap:自定义映射,处理多对一或一对多的映射关系

    这两个属性只能写一个
    -->
    <select id="getUserById" resultType="com.spark.pojo.User">
        select * from user where id = 1;
    </select>


    <!--List<User> getAllUser();-->
    <select id="getAllUser" resultType="com.spark.pojo.User">
        select * from user;
    </select>
</mapper>

最后在测试类中写测试方法

package com.spark;

import com.spark.mapper.UserMapper;
import com.spark.pojo.User;
import com.spark.utils.SqlSessionUtil;
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;

public class MyBatisTest {

    @Test
    public void testUpdate(){
        SqlSession sqlSession = SqlSessionUtil.getSqlSession();
        UserMapper mapper = sqlSession.getMapper(UserMapper.class);
        mapper.updateUser();
        sqlSession.close();
    }

    @Test
    public void testDelete(){
        SqlSession sqlSession = SqlSessionUtil.getSqlSession();
        UserMapper mapper = sqlSession.getMapper(UserMapper.class);
        mapper.deleteUser();
        sqlSession.close();
    }

    @Test
    public void testSelect(){
        SqlSession sqlSession = SqlSessionUtil.getSqlSession();
        UserMapper mapper = sqlSession.getMapper(UserMapper.class);
        User userById = mapper.getUserById();
        System.out.println(userById);
        sqlSession.close();
    }


    @Test
    public void testAllSelect(){
        SqlSession sqlSession = SqlSessionUtil.getSqlSession();
        UserMapper mapper = sqlSession.getMapper(UserMapper.class);
        List<User> list = mapper.getAllUser();
        list.forEach(System.out::println);
        sqlSession.close();
    }
}

5.2 mybatis核心配置

5.2.1 environment(mybatis-config.xml)
 <!--配置连接数据库环境-->
    <!--
        default:设置默认使用的环境的id
    -->
    <environments default="development">
        <!--   
        environment:设置一个具体的数据库的连接环境
        属性:
        id:环境设置的唯一标识,不能重复
        
        -->
        <environment id="development">
            <!--
                transactionManager:设置事务管理器
                属性:
                type:设置事务管理的方式
                type="JDBC/MANAGED"
                JDBC:表示使用JDBC中原生的事务管理方式
                MANAGED:被管理,例如Spring
             
                
             -->
            <transactionManager type="JDBC"/>
            <!--
                datasource:设置数据源
                属性:
                type:设置数据源的类型
                type="POOLED|UNPOOKED|JNDI"
                POOLED:表示使用数据库连接池(使用数据库连接池的话,每一次登录都会使用上一次用过的数据源)
                UNPOOLED:表示不使用数据库连接池
                JNDI:表示使用上下文中的数据源
            
            -->
            <dataSource type="POOLED">
                <property name="driver" value="com.mysql.cj.jdbc.Driver"/>
                <property name="url" value="jdbc:mysql://localhost:3306/mybatis?serverTimezone=UTC"/>
                <property name="username" value="root"/>
                <property name="password" value="123456"/>
            </dataSource>
        </environment>
    </environments>
5.2.2 properties

我们是将数据源放在mybatis映射文件中指定的,但是这样不便于管理,我们可以利用jdbc的方式,将数据源都放在properties文件中,然后在mybatis映射文件中引入properties资源,再把原来property标签中的值改成==${key}==的形式。

来吧,在resource文件下中创建一个jdbc.properties文件

#driver=com.mysql.cj.jdbc.Driver
#建议加上前缀jdbc,防止用错数据源
jdbc.driver=com.mysql.cj.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/mybatis?serverTimezone=UTC
jdbc.username=root
jdbc.password=123456

这里的driver、url、password、username要跟property中的对应

在这里插入图片描述

然后在映射文件中引入该properties资源

 <!--引入properties文件,此后就可以在当前文件中使用${key}的方式访问value-->
    <properties resource="jdbc.properties"/>

最后,将原来的value值改成 ${key} 的形式

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

这是mybatis-config.xml中的标签,用来取别名。比如全类名太长,想给他取一个简短的名字

    <typeAliases>
        <typeAlias type="com.spark.pojo.User" alias="User"></typeAlias>
    </typeAliases>
        <!-- 
 		type:需要起别名的类型
		alias:取的别名
		-->

<!-- 如果没有指定alias的值,默认为类名,而且不区分大小写,在这里就是User/user。上下两个是一样的 -->
    <typeAliases>
        <typeAlias type="com.spark.pojo.User"></typeAlias>
    </typeAliases>

这样的话,我们就可以在UserMapper.xml文件中,用这个别名,来替换全类名

    <select id="getAllUser" resultType="User">
        select * from user;
    </select>

另外还有种比较方便的方式,就是用package标签

    <typeAliases>
        <!-- 通过包名设置别名,则指定包下的所有类型都有默认的别名(即类名) -->
        <package name="com.spark.pojo"/>
    </typeAliases>

注意:别名只能在mybatis的范围中使用

5.2.4 mappers

之前我们在引入mybatis映射文件时,是在mappers标签下的mapper标签中引入的。

    <!--引入mybatis的映射文件。mappers包下的UserMapper.xml文件-->
    <mappers>
        <mapper resource="mappers/UserMapper.xml"/>
    </mappers>

跟取别名类似,我们也可以用打包的方法引入。但这里打包有条件,就是:映射文件必须跟mapper接口同包同名

    <mappers>
        <!--<mapper resource="mappers/UserMapper.xml"/>-->
        <package name="com.spark.mapper"/>
    </mappers>

在这里插入图片描述

乍一看,这两个文件好像并没有在一个目录里,那么让它们同包有意义吗?

当然有,其实在实际的目录中,它们是在同一个文件夹里的。

在这里插入图片描述

5.2.5 创建自定义模板

我们辛辛苦苦敲了这么多配置文件,在多数情况下都是能够以它们作为基础来添加代码的。所以我们可以将这些文件,借助IDEA的自定义模板功能来用它们构建模板。

在这里插入图片描述

这样新建模板就可以直接得到上述代码文件

5.3 mybatis参数

5.3.1 获取参数的两种方式
一种是  ${}		一种是  #{}

${}	本质上是字符串拼接,使用时需要手动加引号 
#{} 本质上是占位符,这个不需要

<select id="getUserByUserName" resultType="User">
        select * from user where name = #{name};
        select * from user where name = '${name}';

</select>

<!-- 
	
 	#{name}在sql语句编译时,会被替代成问号(?),在传递参数的时候,
	会根据传来的数据类型来自动添加引号


	${name}则不会,它传到什么就是什么,不会加引号


	另外,两者的大括号中的内容可以随便取。不过最好按规范来。

	以上都是mapper接口方法的参数为单个字面量类型的情况

-->



这是第一种

在这里插入图片描述

这是第二种

在这里插入图片描述

5.3.2 多个字面量参数
mapper接口方法的参数为多个字面量时
此时mybatis会将参数放在map集合中,以两种方式存储数据
第一种是以arg0、arg1...为键,参数为值
第二种是以param1、param2...为键,参数为值
因此只需要通过${}和#{}访问map集合的键,就可以获取相对应的值
<!--User findUser(String name,Integer gender);-->
    <select id="findUser" resultType="User">
<!--select * from user where name = #{param1} and gender = #{param2};-->
<!--select * from user where name = #{arg0} and gender = #{arg1};-->

        select * from user where name = '${arg0}' and gender = '${arg1}';

    </select>

在这里插入图片描述

既然是存在map集合中,我们可以将数据放在我们自己创建的map集合中啊。

在UserMapper中创建一个接口方法,参数为map集合

    /**
     * 用自己的map
     * @param map
     * @return
     */
    User findUserByMap(Map map);

在测试方法中,自己设置集合的键,并将键的值放入集合中,然后调用mapper方法,将集合传递给参数

    @Test
    public void findUserByMap() {
        SqlSession sqlSession = SqlSessionUtil.getSqlSession();
        UserMapper mapper = sqlSession.getMapper(UserMapper.class);
        Map<String, Object> map = new HashMap<>();
        map.put("name","hutao");
        map.put("gender",0);
        User user = mapper.findUserByMap(map);
        System.out.println(user);
    }

在映射文件中,参数获取就可以跟单个字面量一样使用了

    <!--User findUserByMap(Map map);-->
    <select id="findUserByMap" resultType="User">
        <!--select * from user where name = #{name} and gender = #{gender};-->
        select * from user where name = '${name}' and gender = '${gender}';

    </select>
如果mapper的接口方法参数为实体类的参数类型,只需要通过#{}和${}访问实体类的属性名,就可以获得对应的值。
    /**
     * 插入数据
     * @param user
     */
    void insertUser(User user);
    
    
    
    
    <!--void insertUser(User user);-->
    <insert id="insertUser">
        insert into user values (null,#{name},#{gender},#{age});
    </insert>
    
    
    
        @Test
    public void TestInsertUser() {
        SqlSession sqlSession = SqlSessionUtil.getSqlSession();
        UserMapper mapper = sqlSession.getMapper(UserMapper.class);
        User user = new User(null,"胡桃",0,17);
        mapper.insertUser(user);
        System.out.println(user);
    }

5.3.3 @param注解

它的作用就是修改mybatis中map集合中键的名字。用在接口方法的参数中。

User findUser(@Param("name") String name, @Param("gender")Integer gender);
    <!--User findUser(String name,Integer gender);-->
    <select id="findUser" resultType="User">
        <!--select * from user where name = #{param1} and gender = #{param2};-->
    <!--select * from user where name = #{arg0} and gender = #{arg1};-->

        <!--select * from user where name = '${arg0}' and gender = '${arg1}';-->
     select * from user where name = #{name} and gender = #{gender} ;

    </select>

用了这个注解后,其实还是可以用param1、param2…这样的为键,参数为值。arg0、arg1…是用不了了。

5.3.4 mybatis取别名

我写了一个计数操作

    /**
     * 返回用户数量
     * @return
     */
    Integer UserCount();
    
        <select id="UserCount" resultType="int">
        select count(*) from user;
    </select>
    
    
        @Test
    public void TestUserCount(){
        SqlSession sqlSession = SqlSessionUtil.getSqlSession();
        UserMapper mapper = sqlSession.getMapper(UserMapper.class);
        Integer count = mapper.UserCount();
        System.out.println(count);
    }

运行时没有报错,结果也正确。但为什么?

在resultType中我并没有写哪种类型,照理说整数类型的数据不应该在java.lang.Integer中吗?

其实这是mybatis的别名机制

具体在这看

5.3.5 以map作为结果类型

单条数据的情况

之前我们的resultType中通常都是User。也就是说查询的数据会以User的属性值返回,即使有些属性没有赋值,它依然会以null的值返回,而空值有时候我们并不需要。

    <!--User findUser(String name,Integer gender);-->
    <select id="findUser" resultType="User">
        <!--select * from user where name = #{param1} and gender = #{param2};-->
    <!--select * from user where name = #{arg0} and gender = #{arg1};-->

        <!--select * from user where name = '${arg0}' and gender = '${arg1}';-->
        select * from user where name = #{name} and gender = #{gender} ;

    </select>


    @Test
    public void testFindUser(){
        SqlSession sqlSession = SqlSessionUtil.getSqlSession();
        UserMapper mapper = sqlSession.getMapper(UserMapper.class);
        User user = mapper.findUser("hutao",0);
        System.out.println(user);
    }

这个时候我们可以将map作为返回值(这样只有不为null的字段才会放进map集合中)

    <!--void selectUserByIdToMap();-->
    <select id="selectUserByIdToMap" resultType="map">
        select * from user where id = #{id}
    </select>

    Map<String,Objects> selectUserByIdToMap(@Param("id") Integer id);
        
            @Test
    public void TestSelectUserByIdToMap(){
        SqlSession sqlSession = SqlSessionUtil.getSqlSession();
        UserMapper mapper = sqlSession.getMapper(UserMapper.class);
        Map<String, Objects> map = mapper.selectUserByIdToMap(1);
        System.out.println(map);
    }
多条数据的情况

我想把表格中的数据,都用resultType为map的形式查询出来。首先要想到多条数据肯定是放在集合中的,存放集合的集合,容易想到用list

第一种方式:

List<Map<String,Object>> selectAllUserToMap();


    <select id="selectAllUserToMap" resultType="map">
        select * from user;
    </select>
        
        
            @Test
    public void TestSelectAllUserToMap(){
        SqlSession sqlSession = SqlSessionUtil.getSqlSession();
        UserMapper mapper = sqlSession.getMapper(UserMapper.class);
        List<Map<String, Object>> maps = mapper.selectAllUserToMap();
        maps.forEach(System.out::println);
    }

第二种方式:

使用@MapKey注解

//可以将每条数据转换为map集合放在一个大的map集合中。利用MapKey注解将查询的某个字段的值作为大的map的键
@MapKey("id")
Map<String,Object> selectAllUserToMap();


    <select id="selectAllUserToMap" resultType="map">
        select * from user;
    </select>
        
        
            @Test
    public void TestSelectAllUserToMap(){
        SqlSession sqlSession = SqlSessionUtil.getSqlSession();
        UserMapper mapper = sqlSession.getMapper(UserMapper.class);
        Map<String, Object> maps = mapper.selectAllUserToMap();
        System.out.println(maps);
    }

在这里插入图片描述

5.4 特殊SQL语句的执行

5.4.1 模糊匹配

对于下面的模糊匹配,将报错。

        List<User> selectByLike(@Param("mohu") String mohu);


<select id="selectByLike" resultType="User">
        select * from user where name like '%#{mohu}%'
    </select>
       // #{}在SQL编译中会被编译成?
       // 结果就是'%?%'
        
            @Test
    public void selectByLike(){
        SqlSession sqlSession = SqlSessionUtil.getSqlSession();
        UserMapper mapper = sqlSession.getMapper(UserMapper.class);
        List<User> list = mapper.selectByLike("h");
        list.forEach(System.out::println);
    }

解决方法:

第一种(缺点,可以被SQL注入):

<!--将#{}改成${}-->
    <select id="selectByLike" resultType="User">
        select * from user where name like '%${mohu}%'
    </select>

第二种(比较好的):

字符串拼接函数

    <select id="selectByLike" resultType="User">
    select * from user where name like concat('%',#{mohu},'%');
    </select>
5.4.2 批量删除
    <delete id="deleteMoreUser" >
        delete from user where id in (${ids});
    </delete>

输入:num1,num2
得到:delete from user where id in (num1,num2);
5.4.3 动态查询表

    /**
     * 动态查询表
     * @param tableName
     * @return
     */
    List<User> GetUserList(@Param("tableName")String tableName);



<select id="GetUserLIst" resultType="User">
	select * from ${tableName};
</select>
        
            @Test
    public void TestGetUserList(){
        SqlSession sqlSession = SqlSessionUtil.getSqlSession();
        UserMapper mapper = sqlSession.getMapper(UserMapper.class);
        List<User> list = mapper.GetUserList("user");
        list.forEach(System.out::println);
    }

在这里插入图片描述

5.4.4 添加功能获取自增的主键

就是添加用户的时候,你可以知道这是表格中的第几个。

    /**
     * 添加用户信息并获取添加的主键
     * @param user
     */
    void insertUser_(User user);

/*
useGeneratedKeys="true"
表示使用自增主键

keyProperty="id"
将主键值赋给id,这个id必须是实体类的自增主键属性
*/

    <!--void insertUser_(User user);-->
    <insert id="insertUser_" useGeneratedKeys="true" keyProperty="id">
        insert into user values(null,#{name},#{age},#{gender});
    </insert>
        
        
            @Test
    public void TestInsertUser(){
        SqlSession sqlSession = SqlSessionUtil.getSqlSession();
        UserMapper mapper = sqlSession.getMapper(UserMapper.class);
        User user = new User(null,"牵牛星",25,1);
        mapper.insertUser_(user);
        System.out.println(user);
    }

在这里插入图片描述

5.5 自定义映射resultMap

5.5.1 映射关系处理

当属性名跟字段名映射关系不一致时

emp_id     empId
  • 为查询字段设置跟属性名一致的别名

        <select id="getEmpByEmpId" resultType="Emp">
            select emp_id empId from new_emp where emp_id = #{empId};
        </select>
    
  • 在mybatis的核心配置文件中设置一个全局配置,将下划线写法转化为驼峰写法

     <setting name="mapUnderscoreToCamelCase" value="true"/>
    
  • 使用resultMap自定义映射处理

        <resultMap id="empResultMap" type="Emp">
            <id column="emp_id" property="empId"></id>
            <result column="emp_name" property="empName"></result>
            <result column="age" property="age"></result>
            <result column="gender" property="gender"></result>
        </resultMap>
    <!--
    id:自定义映射的唯一标识
    type:处理映射关系的实体类型
    
    id:设置主键和属性映射的关系
    result:设置普通字段跟属性映射的关系
    -->
    
    
    <!--Emp getEmpByEmpId(@Param("id")Integer id);  这里就不需要写resultType了-->
        <select id="getEmpByEmpId" resultMap="empResultMap">
            select * from new_emp where emp_id = #{empId};
        </select>
    
5.5.2 处理多对一的映射关系

假设这里有一份员工表和一份部门表,我想通过员工id查询该员工的基本信息及部门信息。请问该怎么做?

//Emp类的属性
    private Integer empId;
    private String empName;
    private Integer age;
    private String gender;
    private Dept dept;
//Dept类的属性
private Integer deptId;
private String deptName;

第一种:级联处理

       <resultMap id="edResultMap" type="Emp">
        <id column="emp_id" property="empId"></id>
        <result column="emp_name" property="empName"></result>
        <result column="age" property="age"></result>
        <result column="gender" property="gender"></result>
           
         <!-- 关键看这里 -->  
        <result column="dept_id" property="dept.deptId"></result>
        <result column="dept_name" property="dept.deptName"></result>
           
    </resultMap>
   
   <!--Emp getEmpAndDeptByEmpId(@Param("empId")Integer empId);-->
    <select id="getEmpAndDeptByEmpId" resultMap="edResultMap">
        select
            new_emp.*,new_dept.*
        from new_emp
            left join new_dept
                on new_emp.emp_id = new_dept.dept_id
                 where new_emp.emp_id = #{empId};
    </select>

第二种:association

    <resultMap id="edResultMapTwo" type="Emp">
        <id column="emp_id" property="empId"></id>
        <result column="emp_name" property="empName"></result>
        <result column="age" property="age"></result>
        <result column="gender" property="gender"></result>
        
        <!-- 
	association:用来处理实体类类型的属性(Dept dept)
	property:这个属性的属性名
	javaType:该属性的实体类类型
-->
        <association property="dept" javaType="Dept">
            <id column="dept_id" property="deptId"></id>
            <result column="dept_name" property="deptName"></result>
        </association>
        
    </resultMap>
   
   <!--Emp getEmpAndDeptByEmpId(@Param("empId")Integer empId);-->
    <select id="getEmpAndDeptByEmpId" resultMap="edResultMapTwo">
        select
            new_emp.*,new_dept.*
        from new_emp
            left join new_dept
                on new_emp.emp_id = new_dept.dept_id
                 where new_emp.emp_id = #{empId};
    </select>

第三种:分步查询

分步查询就是先查询一个表,获得一个字段,然后根据这个字段去查询另外一个表

作为关联两个表的字段,在部门表跟员工表之间,就是dept_id

首先,我们要先查询员工表:

  <!--Emp getEmpAndDeptByStep(@Param("empId")Integer empId);-->
    <select id="getEmpAndDeptByStepOne" resultMap="edResultMapByStep">
        select * from new_emp where emp_id = #{empId};
    </select>

然后去设计第二张表的查询(在deptmapper中写接口方法,在deptmapper.xml中写sql语句)

<mapper namespace="com.spark.mapper.DeptMapper">


    <!--Dept getEmpAndDeptByStepTwo(@Param("deptId") Integer deptId);-->
    <select id="getEmpAndDeptByStepTwo" resultType="Dept">
        select * from new_dept where dept_id = #{deptId};
    </select>

</mapper>

接着设置resultMap

    <resultMap id="edResultMapByStep" type="Emp">
        <id column="emp_id" property="empId"></id>
        <result column="emp_name" property="empName"></result>
        <result column="age" property="age"></result>
        <result column="gender" property="gender"></result>
        <association property="dept"
                     select="com.spark.mapper.DeptMapper.getEmpAndDeptByStepTwo"
                     column="dept_id"
        ></association>
    </resultMap>

<!-- 
select:分布查询sql的唯一标识
column:根据查询出的某个字段作为分布查询sql的条件
-->

效果就是,执行了两句sql语句

在这里插入图片描述

5.5.3 分步查询的好处

延迟加载:

        <!--延迟加载-->
        <setting name="lazyLoadingEnabled" value="true"/>

按需加载:可以根据需要来查询表,即使设置了多张表的查询语句,如果说查询的某个字段只需要查询一张表,那么就只会执行那一张表的查询语句

        <!--按需加载,该设置默认为false-->
        <setting name="aggressiveLazyLoading" value="false"/>

这两个setting要一起使用才有效。

setting是全局设置,按照上面的代码,整个mybatis查询表格的时候,就都会开启延迟加载,如果我想要某个语句立即加载,那该怎么弄。

可以在association中的fetchType属性中设置

    <resultMap id="edResultMapByStep" type="Emp">
        <id column="emp_id" property="empId"></id>
        <result column="emp_name" property="empName"></result>
        <result column="age" property="age"></result>
        <result column="gender" property="gender"></result>
        <association property="dept"
                     select="com.spark.mapper.DeptMapper.getEmpAndDeptByStepTwo"
                     column="dept_id"
                     
                     fetchType="eager"
                     
        ></association>
    </resultMap>

在这里插入图片描述

5.5.4 处理一对多

根据一个部门,查询它下面的所有员工信息

collection:用于一对多(处理集合类型的属性)

ofType:设置集合类型的属性中存储的数据的类型

    <resultMap id="deptAndEmpResultMap" type="Dept">
        <id column="dept_id" property="deptId"></id>
        <result column="dept_name" property="deptName"></result>
        <collection property="emps" ofType="Emp">
            <id column="emp_id" property="empId"></id>
            <result column="emp_name" property="empName"></result>
            <result column="age" property="age"></result>
            <result column="gender" property="gender"></result>
        </collection>
    </resultMap>


    <!--Dept getDeptAndEmpByDeptId(@Param("deptId") Integer deptId);-->
    <select id="getDeptAndEmpByDeptId" resultMap="deptAndEmpResultMap">
        select *
        from new_dept
        left join new_emp
        on new_dept.dept_id = new_emp.dept_id
        where new_dept.dept_id = #{deptId}
    </select>

在这里插入图片描述

分步查询也可以,具体见代码

5.6 动态SQL

随着用户的输入或外部条件的变化而变化的SQL语句,称作动态SQL。之前写的代码中,查询的条件是固定死的,只要有一项不满足就会查不到数据。

 <select id = "list" resultType = "com.itheima.pojo.Emp">		
    select * from emp where name like concat('%',#{name},'%') and gender = #{gender} and entrydate between #{begin} and #{end} order by update_time desc 
    </select>
//比如下面的方法,尽管要查询的表中只有一个人的名字含有“张”,但由于后面的字段为null,无法匹配。所以查询不到结果。

@Test
    public void testList(){
        List<Emp> empList = empMapper.list("张", null, null, null);
        System.out.println(empList);
    }

此时,我们需要用到动态SQL。

5.6.1 if标签
<!-- 
 <if>:用于条件判断。使用test属性进行判断,如果条件为true,则拼接SQL。比如下面的例子
如果name不是null,就执行下面的SQL语句。
 -->

<if test = "name!=null">
 name like concat('%',#{name},'%')
</if>
<!-- 用if标签改造后:-->
<select id="list" resultType="com.itheima.pojo.Emp">
        select *
     	from emp
     	where
            <if test="name != null">
                name like concat('%', #{name}, '%')
            </if>
            <if test="gender != null">
                and gender = #{gender}
            </if>
            <if test="begin != null and end != null">
                and entrydate between #{begin} and #{end}
            </if>
        order by update_time desc
    </select>

在这里插入图片描述

//但是如果执行下面的方法,将会报错
@Test
    public void testList(){
        List<Emp> empList = empMapper.list(null, (short)1, null, null);
        System.out.println(empList);
    }

可以看看哪里报了个错:

在这里插入图片描述

在这里插入图片描述

报错的位置靠近“and gender = 1”,我们再来看下xml代码,发现如果执行了上面的方法,那么SQL语句中就多了个and,因为我们将name赋值为null,在xml的if标签中,就会跳过 name like concat(‘%’, #{name}, ‘%’),转而执行 and gender = #{gender},显然在and前面没有连接的字段,出现了语法错误。但是我们又不能将and删去,删去之后 ==List empList = empMapper.list(“Tom”, (short)1, null, null);==又会报错。此时需要想办法解决。

<select id="list" resultType="com.itheima.pojo.Emp">
        select *
     	from emp
     	where
            <if test="name != null">
                name like concat('%', #{name}, '%')
            </if>
            <if test="gender != null">
                and gender = #{gender}
            </if>
            <if test="begin != null and end != null">
                and entrydate between #{begin} and #{end}
            </if>
        order by update_time desc
    </select>
5.6.2 where标签

XML提供了where标签替换where关键字来解决上面的问题。

where标签的作用:

  • 根据where中的子标签动态判断条件,如果子标签里的条件都不成立,就不会生成where关键字。如果有一个条件成立,就会生成where关键字。
  • 去掉sql语句中多余的 andor
<select id="list" resultType="com.itheima.pojo.Emp">
        select *
     	from emp
     	<where>
            <if test="name != null">
                name like concat('%', #{name}, '%')
            </if>
            <if test="gender != null">
                and gender = #{gender}
            </if>
            <if test="begin != null and end != null">
                and entrydate between #{begin} and #{end}
            </if>
    	</where>
        order by update_time desc
    </select>
5.6.3 set标签

对于下面的代码,在执行更新操作时,如果有些字段没有赋值的话,在表中的对应字段会变成null。

 @Test
    public void testUpdate2(){
  //构造员工对象
  	Emp emp = new Emp();
  	emp.setId(19);
  	emp.setUsername("Tom222");
    emp.setName("汤姆222");
    emp.setGender((short)1);
    emp.setUpdateTime(LocalDateTime.now());

  //执行更新员工操作
     empMapper.update2(emp);
   }

在这里插入图片描述

在XML中使用if标签就可以很好地解决。

 <update id="update2">
        update emp
        set
            <if test="username != null">username = #{username},</if>
            <if test="name != null">name = #{name},</if>
            <if test="gender != null">gender = #{gender},</if>
            <if test="image != null">image = #{image},</if>
            <if test="job != null">job = #{job},</if>
            <if test="entrydate != null">entrydate = #{entrydate},</if>
            <if test="deptId != null">dept_id = #{deptId},</if>
            <if test="updateTime != null">update_time = #{updateTime}</if>
        where id = #{id}
    </update>

但是又遇到一个问题,如果赋值语句是下面的的话,又会出现语法错误

@Test
    public void testUpdate2(){
  //构造员工对象
  	Emp emp = new Emp();
  	emp.setId(19);
  	emp.setUsername("Tom222333");
  //执行更新员工操作
     empMapper.update2(emp);
   }

在这里插入图片描述

结果发现 ==where id === 的前面多了个逗号。

在这里插入图片描述

我们再来看一眼XML配置文件中的代码。我们这次只修改了第一个字段,所以只执行了第一个if标签中的SQL语句,后面if标签中的SQL语句就直接略过了,因此会执行到 username = #{username},,就是这里多了个逗号。

<update id="update2">
        update emp
        set
            <if test="username != null">username = #{username},</if>
            <if test="name != null">name = #{name},</if>
            <if test="gender != null">gender = #{gender},</if>
            <if test="image != null">image = #{image},</if>
            <if test="job != null">job = #{job},</if>
            <if test="entrydate != null">entrydate = #{entrydate},</if>
            <if test="deptId != null">dept_id = #{deptId},</if>
            <if test="updateTime != null">update_time = #{updateTime}</if>
        where id = #{id}
    </update>

XML也提供了set标签来代替set关键字解决这个问题。

set标签作用:

  • 将所有字段进行包裹
  • 去除多余的逗号(用在update中)
 <update id="update2">
        update emp
        <set>
            <if test="username != null">username = #{username},</if>
            <if test="name != null">name = #{name},</if>
            <if test="gender != null">gender = #{gender},</if>
            <if test="image != null">image = #{image},</if>
            <if test="job != null">job = #{job},</if>
            <if test="entrydate != null">entrydate = #{entrydate},</if>
            <if test="deptId != null">dept_id = #{deptId},</if>
            <if test="updateTime != null">update_time = #{updateTime}</if>
        </set>
        where id = #{id}
    </update>
5.6.4 foreach标签(批量操作)
  • SQL语句

    delete from emp where id in (18,19,20);
    
  • 接口

        public void deleteByIds(List<Integer> ids);
    
  • XML

    <!--批量删除员工 (18,19,20)-->
        <!--
            collection: 遍历的集合
            item: 遍历出来的元素
            separator: 分隔符
            open: 遍历开始前拼接的SQL片段
            close: 遍历结束后拼接的SQL片段
        -->
        <delete id="deleteByIds">
            delete  from emp where id in
            <foreach collection="ids" item="id" separator="," open="(" close=")">
                #{id}
            </foreach>
        </delete>
    
  • 单元测试

     //批量删除员工 - 18, 19, 20
       @Test
       public void testDeleteByIds(){
           List<Integer> ids = Arrays.asList(18, 19, 20);
           empMapper.deleteByIds(ids);
       }
    
5.6.5 sql、include标签
  • sql标签:定义可重用的SQL片段
  • include标签:通过属性refid,指定包含的sql片段

面对以下的代码,如果要修改表名或者字段名的话,则在两个select标签中都要修改,这就导致代码的复用性差

在这里插入图片描述

XML文件提供了解决方案。

 <select id="list" resultType="com.itheima.pojo.Emp">
     <!-- 将refid属性指定的sql片段引用进来 -->
        <include refid="commonSelect"/>
        <where>
            <if test="name != null">
                name like concat('%', #{name}, '%')
            </if>
            <if test="gender != null">
                and gender = #{gender}
            </if>
            <if test="begin != null and end != null">
                and entrydate between #{begin} and #{end}
            </if>
        </where>
        order by update_time desc
    </select>

<!-- 给sql片段指定唯一标识,用于include引用 -->
<sql id="commonSelect">
        select id, username, password, name, gender, image, job, entrydate, dept_id, create_time, update_time
        from emp
    </sql>

在这里插入图片描述

5.6.6 trim标签
<!-- 
prefix、suffix:在trim标签中内容前后添加指定内容
prefixOverrides、suffixOverrides:在trim标签中内容前后删除指定内容
-->
<select>
select * from dept
    <trim prefix="where">
        emp_name = #{empName}
    </trim>
</select>

等同于 select * from dept where emp_name = #{empName}

5.7 mybatis的缓存机制

一级缓存

一级缓存是sqlsession级别的,就是说,使用同一个sqlsession执行sql语句,只执行一次,并将sql语句的查询结果存放在缓存中,下一次再使用相同的查询语句时就可以直接在缓存中查询。

    @Test
    public void testGetEmpById(){
        SqlSession sqlSession = SqlSessionUtil.getSqlSession();
        CacheMapper mapper = sqlSession.getMapper(CacheMapper.class);
        Emp emp1 = mapper.getEmpById(1);
        System.out.println(emp1);
        Emp emp2 = mapper.getEmpById(1);
        System.out.println(emp2);
    }

如图所示,使用同一个sqlsession,sql语句只执行了一次,第二次执行相同的语句时直接从缓存中获取。

在这里插入图片描述

一级缓存失效情况:

  • 不同的Sqlsession对应不同的一级缓存
  • 同一个SqlSession但是查询条件不同
  • 同一个SqlSession两次查询期间都执行了任何一次增删改操作
  • 同一个SqlSession两次查询期间手动清空了缓存
二级缓存

二级缓存是SqlSessionFactory级别的,即通过同一个SqlSessionFactory所获取的SqlSession的SqlSession对象,查询的数据会被缓存,再通过同一个SqlSessionFactory所获取的SqlSession查询相同的数据会从缓存中获取

二级缓存开启条件:

  • 在核心配置文件中,设置全局变量cacheEnabled=“true”,默认为true
  • 在映射文件(CacheMapper.xml)中设置标签
  • 二级缓存必须在SqlSession关闭或提交之后有效
  • 查询的数据所转换的实体类类型必须实现序列化的接口(implements Serializable )

二级缓存失效情况:两次查询之间执行了任意增删改

    @Test
    public void testCache() throws IOException {
        InputStream is = Resources.getResourceAsStream("mybatis-config.xml");
        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(is);
        SqlSession sqlSession1 = sqlSessionFactory.openSession(true);
        CacheMapper mapper1 = sqlSession1.getMapper(CacheMapper.class);
        Emp emp1 = mapper1.getEmpById(1);
        System.out.println(emp1);
        sqlSession1.close();
        SqlSession sqlSession2 = sqlSessionFactory.openSession(true);
        CacheMapper mapper2 = sqlSession2.getMapper(CacheMapper.class);
        Emp emp2 = mapper2.getEmpById(1);
        System.out.println(emp2);

    }

在这里插入图片描述

Cache Hit Ratio 缓存命中率,不为零则说明缓存中存在。

Cache标签中可以配置属性,自行了解。

缓存的查询顺序

先查询二级缓存,没有命中,则查询一级缓存。又没命中,则查询数据库。

关闭SqlSession之后,一级缓存中的数据会写入二级缓存。

整合第三方缓存EHCache

自己查资料

mybatis逆向工程

正向工程:先创建Java实体类,由框架负责根据实体类生成数据库表。Hibernate支持正向工程。

逆向工程:先创建数据库表,由框架负责根据数据库表,反向生成如下资源:

  • Java实体类
  • Mapper接口
  • Mapper映射文件

主要通过下面的配置文件(pom.xml)

<?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>com.spark</groupId>
    <artifactId>mybatis_mbg</artifactId>
    <version>1.0-SNAPSHOT</version>
    <packaging>jar</packaging>

    <properties>
        <maven.compiler.source>8</maven.compiler.source>
        <maven.compiler.target>8</maven.compiler.target>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    </properties>


    <dependencies>
        <!-- mybatis核心-->
        <dependency>
            <groupId>org.mybatis</groupId>
            <artifactId>mybatis</artifactId>
            <version>3.5.9</version>
        </dependency>


        <!--junit测试-->
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.13.1</version>
            <scope>test</scope>
        </dependency>
        <!--mysql驱动-->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>8.0.29</version>
        </dependency>

    </dependencies>

    <build>

        <!--构建过程中用到的插件-->
        <plugins>

         <plugin>
             <groupId>org.mybatis.generator</groupId>
             <artifactId>mybatis-generator-maven-plugin</artifactId>
             <version>1.3.0</version>
             <dependencies>

                 <!--逆向工程核心依赖-->
                 <dependency>
                     <groupId>org.mybatis.generator</groupId>
                     <artifactId>mybatis-generator-core</artifactId>
                     <version>1.3.0</version>
                 </dependency>

                 <!--mysql驱动-->
                 <dependency>
                     <groupId>mysql</groupId>
                     <artifactId>mysql-connector-java</artifactId>
                     <version>8.0.29</version>
                 </dependency>
             </dependencies>
         </plugin>


        </plugins>


    </build>

</project>

新建一个配置文件,必须命名为generatorConfig.xml

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE generatorConfiguration
        PUBLIC "-//mybatis.org//DTD MyBatis Generator Configuration 1.0//EN"
        "http://mybatis.org/dtd/mybatis-generator-config_1_0.dtd">

<generatorConfiguration>


    <!--

        targetRuntime:执行生成的逆向工程的版本
        MyBatis3:生成带条件的CRUD(奢华尊享版)
        MyBatis3Simple:生成基本的CRUD(清新简洁版)


    -->

    <context id="DB2Tables" targetRuntime="MyBatis3">
        <!-- 去除生成的注解 -->
        <commentGenerator>
            <property name="suppressAllComments" value="true"/>
        </commentGenerator>



        <!-- 数据库连接配置 -->
        <!-- 注意xml中不支持&,用&amp;代替 -->
        <jdbcConnection driverClass="com.mysql.cj.jdbc.Driver"
                        connectionURL="jdbc:mysql://localhost:3306/mybatis?serverTimezone=UTC"
                        userId="root" password="123456"></jdbcConnection>

        <!-- 处理NUMERIC和DECIMAL类型的策略 -->
        <javaTypeResolver>
            <property name="forceBigDecimals" value="false" />
        </javaTypeResolver>

        <!-- 配置pojo生成的位置 -->
        <javaModelGenerator targetPackage="com.spark.pojo"  targetProject="./src/main/java">
            <property name="enableSubPackages" value="true" />
            <property name="trimStrings" value="true" />
        </javaModelGenerator>

        <!-- 配置sql映射文件的生成位置 -->
        <sqlMapGenerator targetPackage="com.spark.mapper" targetProject="./src/main/resources">
            <property name="enableSubPackages" value="true" />
            <property name="trimStrings" value="true" />
        </sqlMapGenerator>

        <!-- 配置dao接口的生成位置 -->
        <javaClientGenerator type="XMLMAPPER" targetPackage="com.spark.mapper"
                             targetProject="./src/main/java">
            <property name="enableSubPackages" value="true" />
            <property name="trimStrings" value="true" />
        </javaClientGenerator>

        <!-- 指定逆向依据的数据表 -->
        <!--tableName设置为*号,可以对应所有表,此时不写domainObjectName-->
        <!--domainObjectName属性指定生成出来的实体类的类名-->
        <table tableName="new_emp" domainObjectName="Emp"></table>
        <table tableName="new_dept" domainObjectName="Dept"></table>
    </context>
</generatorConfiguration>

配置完成后,双击这个。

在这里插入图片描述

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值