天天看点

聊聊MyBatis的历史

目录

​​序言​​

​​原生的mybatis(使用xml)​​

​​关于别名​​

​​关于namespace​​

​​原生的mybatis(使用注解)​​

​​Mybatis与spring​​

​​MapperFactoryBean​​

​​SqlSessionDaoSupport ​​

​​Mybatis与springBoot​​

​​Mybatis与mybatis-plus​​

序言

最近项目中,需要在mysql里面新增一个表,然后套用mybatis做增删改查,然后再mapper.java的方法上写注解。写着写着就感觉很烦,为什么呢?看下面:  

@Insert({
        "INSERT INTO " + TABLE_NAME + INSERT_COLUMNS +
            " VALUES" +
            "(#{id},now(),now(),#{idxConfigId},#{userId},#{ownerId},#{status}," +
            " #{ukIdempotent},#{startTime},#{endTime},#{extraInfo}," +
            "  #{awardResult},#{boxConfig},#{currentCount},#{treeId},#{target},#{scope}   )"
    })
int insertFinanceInfo(FarmFinanceInfoDO financeInfoDO);



@Update({ "update "+TABLE_NAME+" set " +
        " idx_config_id = #{idxConfigId}, " +
        " owner_id = #{ownerId},status = #{status}, " +
        " uk_idempotent = #{ukIdempotent}, " +
        " start_time = #{startTime,jdbcType=TIMESTAMP}, " +
        " end_time = #{endTime,jdbcType=TIMESTAMP}, " +
        " current_count = #{currentCount}, " +
        " tree_id = #{treeId},target = #{target}, scope = #{scope}, " +
        " box_config = #{boxConfig},award_result = #{awardResult},  " +
        " extra_info = #{extraInfo} " +
        "  where id = #{id} and user_id= #{userId}" })
int updateFinanceInfo(FarmFinanceInfoDO financeInfoDO);      

其实我是特别不喜欢在java文件里写这种很长的注解的,感觉特别乱,我甚至愿意把它放到xml里面。后来在网上搜了一下原来还有一个叫mybatis-plus的组件,看着还不错,然后在应用过程中也发生了一下问题,问题的根源就是自己还是急于求成,直接在网上搜一下现成的不成体系的代码就用,后面自然一堆问题。然后这两天就稍微挤出一点时间,把mybatis的使用,从最开始的原生xml,到注解,到spring,到spring-boot到mybatis-plus的使用都梳理了一遍。

原生的mybatis(使用xml)

POM如下:

<!-- https://mvnrepository.com/artifact/mysql/mysql-connector-java -->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>8.0.18</version>
        </dependency>

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

下面mybatis的配置文件

<?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>

    <!-- 别名 -->
    <typeAliases>
        <package name="entity"/>
    </typeAliases>
    <!-- 数据库环境 -->
    <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/test_db?characterEncoding=UTF-8"/>
                <property name="username" value="root"/>
                <property name="password" value="123456"/>
            </dataSource>
        </environment>
    </environments>
    <!-- 映射文件 -->
    <mappers>
        <mapper resource="mapper/user.xml"/>
    </mappers>

</configuration>      

下面是某个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="xml">
    <select id="listUser" resultType="user">
        select * from  user;
    </select>
</mapper>      

这个mapper里面有个namespace,我一直不知道它是什么意思。没关系这个namespace先随便写,一会就知道它是什么意思了。

增加entity

package entity;

/**
 * @program: parent_pro
 * @description:
 * @author: 渭水
 * @create: 2021/07/21
 */
public class User {
    private int id;
    private String username;
    private String  password;
    private String nickname;
    // 省略getset
}      

最后的启动类

package xml;

import entity.User;
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;
import java.util.List;

/**
 * @program: parent_pro
 * @description:
 * @author: 渭水
 * @create: 2021/07/21
 */
public class MyBatisWithXML {
    public static void main(String[] args) {
        String resource = "mybatis-config.xml";
        InputStream inputStream = null;
        try {
            inputStream = Resources.getResourceAsStream(resource);
        } catch (IOException e) {
            e.printStackTrace();
        }
        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
        SqlSession session = sqlSessionFactory.openSession();
        // 最后通过 session 的 selectList() 方法调用 sql 语句 listStudent
        List<User> userList = session.selectList("listUser");
        for (User student : userList) {
            System.out.println("ID:" + student.getId() + ",NAME:" + student.getUsername());
        }
    }
}      

我们看看整个项目个结构图:

聊聊MyBatis的历史

数据库那边,我已经建立好了,大家看看:

聊聊MyBatis的历史

运行项目就能看到:

聊聊MyBatis的历史

OK,至此项目已经可以运行了。

然后我们分析一下代码:

关于别名

一段一段看

<select id="listUser" resultType="user">
        select * from  user limit 5;
</select>      

这个里面的resultType,我们不用看框架,大概就懂它指的是把下面的sql语句执行的结果,包装成什么类型。

那么光写个user,mybatis知道这个user是个什么东西么?

<!-- 别名 -->
<typeAliases>
        <package name="entity"/>
</typeAliases>      

上面一段代码的语义就是,把entity下面的实体都加一个别名,而别名就是类名。所以mybatis就知道上面的user其实对应的就是entity.User这个类。

关于namespace

上面已经有了一个mapper,我现在再加一个mapper如下:

聊聊MyBatis的历史

 和之前那个的区别就是一个limit 5,一个limit 10

然后让mybatis的主配置文件感知到这个user2.xml

<!-- 映射文件 -->
<mappers>
        <mapper  resource="mapper/user.xml"/>
        <mapper  resource="mapper/user2.xml"/>
</mappers>      

然后运行代码会发现:

聊聊MyBatis的历史

 说的很清楚listUser这个id重复了。

在线上工程里,要保证所有的sql的id都不重复有点困难,所以引入了namespace的概念。如果一旦有重复的id,那么session调用的时候得指定namespace。

List<User> userList = session.selectList("xml.listUser");      

关于resultMap

上面的例子里咱们的java类都是简单的字符串和数字,如果是一个比较复杂的结构呢?例如一个Family里面有个list,每个元素都是一个Person。怎么办?DB里面对于的表里面list使用json存储的。怎么把json转化成咱们的Person呢?

这里就要提到一个resultMap这个东西了

我们先看怎么使用

<resultMap id="res_family" type="com.alibaba.entity.Family">

        <result column="family_name" property="familyName"/>


        <result column="car" property="car"
                typeHandler="com.alibaba.entity.handler.JsonTypeHandler"
                javaType="com.alibaba.entity.Car"/>

        <result column="family_persion_list" property="familyPersionList"
                typeHandler="com.alibaba.entity.handler.ListTypeHandler"
                javaType="com.alibaba.entity.Lover"/>
    </resultMap>

    <select  resultMap="res_family" id="getAll">
        select * from family
    </select>




    <insert id="insert">
        insert into family (family_name,car,family_persion_list)
        values( #{familyName}
               ,#{car ,javaType=com.alibaba.entity.Car, typeHandler = com.alibaba.entity.handler.JsonTypeHandler}
               ,#{familyPersionList ,javaType=com.alibaba.entity.Person, typeHandler = com.alibaba.entity.handler.ListTypeHandler}
              )
    </insert>      

通过上面的代码,我们就是猜都能猜处理,使用getAll查询的时候,结果使用res_family来组织,最终装配成com.alibaba.entity.Family。mysql表里面的family_persion_list自动被转化成Lover。这里面涉及到两个Handler。

package com.alibaba.entity.handler;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.apache.ibatis.type.BaseTypeHandler;
import org.apache.ibatis.type.JdbcType;

import java.io.IOException;
import java.sql.CallableStatement;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;

public class JsonTypeHandler<T> extends BaseTypeHandler<T> {

    private static ObjectMapper objectMapper;
    private Class<T> type;
    static {
        objectMapper = new ObjectMapper();
    }

    public JsonTypeHandler(Class<T> type) {

        if (type == null) {
            throw new IllegalArgumentException("Type argument cannot be null");
        }
        this.type = type;
    }

    private  T parse(String json) {
        try {
            if(json == null || json.length() == 0) {
                return null;
            }
            return objectMapper.readValue(json, type);
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

    private String toJsonString(Object obj) {
        try {
            return objectMapper.writeValueAsString(obj);
        } catch (JsonProcessingException e) {
            throw new RuntimeException(e);
        }
    }

    @Override
    public T getNullableResult(ResultSet rs, String columnName) throws SQLException {
        return (T) parse(rs.getString(columnName));
    }

    @Override
    public T getNullableResult(ResultSet rs, int columnIndex) throws SQLException {
        return (T) parse(rs.getString(columnIndex));
    }

    @Override
    public T getNullableResult(CallableStatement cs, int columnIndex) throws SQLException {
        return (T) parse(cs.getString(columnIndex));
    }

    @Override
    public void setNonNullParameter(PreparedStatement ps, int columnIndex, T parameter, JdbcType jdbcType) throws SQLException {
        ps.setString(columnIndex, toJsonString(parameter));

    }

}



package com.alibaba.entity.handler;


import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.TypeReference;
import org.apache.commons.lang3.StringUtils;
import org.apache.ibatis.type.JdbcType;
import org.apache.ibatis.type.MappedJdbcTypes;
import org.apache.ibatis.type.TypeHandler;
import org.springframework.util.CollectionUtils;

import java.sql.CallableStatement;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;

/**
 * 处理jsonArray字符串为pojoList
 *
 *
 * @author zj
 * @date 2020/1/8
 */
@MappedJdbcTypes({JdbcType.VARCHAR})
public class ListTypeHandler<T extends Object> implements TypeHandler<List<T>> {

    private List<T> getListByJsonArrayString(String content) {
        if (StringUtils.isEmpty(content)) {
            return new ArrayList<>();
        }
        return JSON.parseObject(content, new TypeReference<ArrayList<T>>() {
        });

    }

    /**
     * 用于定义在Mybatis设置参数时该如何把Java类型的参数转换为对应的数据库类型
     *
     * <PRE>
     * PreparedStatement pstmt = con.prepareStatement("UPDATE EMPLOYEES
     * SET SALARY = ? WHERE ID = ?");
     * pstmt.setBigDecimal(1, 153833.00)
     * pstmt.setInt(2, 110592)
     * </PRE>
     *
     * @param preparedStatement An object that represents a precompiled SQL statement
     * @param i                 当前参数的位置
     * @param t           当前参数的Java对象
     * @param jdbcType          当前参数的数据库类型
     * @throws SQLException
     */
    @Override
    public void setParameter(PreparedStatement preparedStatement, int i, List<T> t, JdbcType jdbcType) throws SQLException {
        if (CollectionUtils.isEmpty(t)) {
            preparedStatement.setString(i, null);
        } else {
            preparedStatement.setString(i, JSON.toJSONString(t));
        }
    }

    @Override
    public List<T> getResult(ResultSet resultSet, String s) throws SQLException {
        return getListByJsonArrayString(resultSet.getString(s));
    }

    @Override
    public List<T> getResult(ResultSet resultSet, int i) throws SQLException {
        return getListByJsonArrayString(resultSet.getString(i));
    }

    @Override
    public List<T> getResult(CallableStatement callableStatement, int i) throws SQLException {
        return getListByJsonArrayString(callableStatement.getString(i));
    }
}

      

大概能看懂 就是一个把json转成pojo,一个把json转成List<Pojo>。

对应的sql语句如下:

CREATE TABLE `family` (
  `family_name` varchar(255) COLLATE utf8mb4_bin DEFAULT NULL,
  `car` json DEFAULT NULL,
  `family_persion_list` json DEFAULT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;      

对应的实体类

package com.alibaba.entity;

import lombok.Data;

import java.util.List;

@Data
public class Family {
    private String familyName;
    private Car car;
    private  List<Lover> familyPersionList;
}      

上面的Car,Lover就是两个最简单的POJO 里面的字段大家自己写吧。

原生的mybatis(使用注解)

@Mapper
public interface PersonMapper {

    @Select("select id,name ,age from person where id = #{id}")
    Person getPersonById(int id);

    @Select("<script>"
            +"select * from person where 1=1"
            +"<if test='name2 != null'> and name = #{name2}"   +"</if>"
            +"<if test='age != null'> and age = #{age}"   +"</if>"
            +"</script>")
    List<Person> getList(@Param("name2") String name,@Param("age") String age);

    @SelectProvider(type = UserDaoProvider.class, method = "getPersonByPara")
    Person getPersonByPara(@Param("name")  String name, @Param("age") String age);
}      

如上面所示

调用的时候就直接写

PersonMapper personMapper = session.getMapper(PersonMapper.class);
        Person result = personMapper.getPersonByPara("渭水","");
        List<Person> list = personMapper.getList(null,"25");      

其实,这就回到我在序言里说的了,在java代码里写脚本语言很丑,代码很难读。

我们可以用一个单独的java方法来产生sql语句,如下;

@Mapper
public interface PersonMapper {

    @SelectProvider(type = UserDaoProvider.class, method = "getPersonByPara")
    Person getPersonByPara(@Param("name")  String name, @Param("age") String age);
}


public class UserDaoProvider {

    public String getPersonByPara(Map<String, Object> para) {
        String sql = new SQL() {
            {
                SELECT("id, name, age");
                FROM("person");
                if (StringUtils.isNotBlank("" + para.get("name"))) {
                    WHERE("name='" + para.get("name")+"'");
                }
                if (StringUtils.isNotBlank("" + para.get("age"))) {
                    WHERE("age=" + para.get("age"));
                }
            }
        }.toString();
        System.out.println("----" + sql);
        return sql;
    }
}      

Mybatis与spring

通过上面我们可以看到,不管是用xml配置,还是直接在java文件里写注解,最终都是通过SqlSession 来指向下一步的操作的。

所以,就算我们把mybatis与spring整合,其实核心原理还是依赖SqlSession 的。

第一步,我们先去掉之前的mybatis依赖,改成

<dependency>
  <groupId>org.mybatis</groupId>
  <artifactId>mybatis-spring</artifactId>
  <version>2.0.6</version>
</dependency>      

然后在spring的xml里面加上

<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
  <property name="dataSource" ref="dataSource" />
</bean>      

dataSource的相关代码我这边就赘述。

然后,具体来说,整合的思路有两种:

MapperFactoryBean

把我们的mapper放到MapperFactoryBean里面

public interface UserMapper {
  @Select("SELECT * FROM users WHERE id = #{userId}")
  User getUser(@Param("userId") String userId);
}      
<bean id="userMapper" class="org.mybatis.spring.mapper.MapperFactoryBean">
  <property name="mapperInterface" value="org.mybatis.spring.sample.mapper.UserMapper" />
  <property name="sqlSessionFactory" ref="sqlSessionFactory" />
</bean>      

使用的时候

public class FooServiceImpl implements FooService {

  private final UserMapper userMapper;

  public FooServiceImpl(UserMapper userMapper) {
    this.userMapper = userMapper;
  }

  public User doSomeBusinessStuff(String userId) {
    return this.userMapper.getUser(userId);
  }
}      

我们怎么说也是写了年代码的人了,稍微往下追一下吧

先说userMapper它最后是MapperFactoryBean,哦,FactoryBean,来大家看一下,也就是说最终userMapper是MapperFactoryBean调用getObject的结果,我们看看代码

聊聊MyBatis的历史
public abstract class SqlSessionDaoSupport extends DaoSupport {

  private SqlSessionTemplate sqlSessionTemplate;


  public void setSqlSessionFactory(SqlSessionFactory sqlSessionFactory) {
    if (this.sqlSessionTemplate == null || sqlSessionFactory != this.sqlSessionTemplate.getSqlSessionFactory()) {
      this.sqlSessionTemplate = createSqlSessionTemplate(sqlSessionFactory);
    }
  }


  protected SqlSessionTemplate createSqlSessionTemplate(SqlSessionFactory sqlSessionFactory) {
    return new SqlSessionTemplate(sqlSessionFactory);
  }


  public SqlSession getSqlSession() {
    return this.sqlSessionTemplate;
  }


}


public class SqlSessionTemplate implements SqlSession, DisposableBean {
//......
}      

哦原来SqlSessionTemplate 本来就是SqlSession哦

SqlSessionDaoSupport 

第二种方法是SqlSessionDaoSupport 

public class UserDaoImpl extends SqlSessionDaoSupport implements UserDao {
  public User getUser(String userId) {
    return getSqlSession().selectOne("org.mybatis.spring.sample.mapper.UserMapper.getUser", userId);
  }
}      

然后把sqlSessionFactory注入进去就好

<bean id="userDao" class="org.mybatis.spring.sample.dao.UserDaoImpl">
  <property name="sqlSessionFactory" ref="sqlSessionFactory" />
</bean>      

其实两个方法也差不多,

那我现在又有另一个mapper了,怎么办?再写一遍下面的代码么?

<bean id="userMapper2" class="org.mybatis.spring.mapper.MapperFactoryBean">
  <property name="mapperInterface" value="org.mybatis.spring.sample.mapper.UserMapper" />
  <property name="sqlSessionFactory" ref="sqlSessionFactory" />
</bean>      

不是这样的,我们有三种方案:

<!-- 方案1 -->
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
  <property name="basePackage" value="org.mybatis.spring.sample.mapper" />
</bean>


<!-- 方案2 -->

<beans xmlns="http://www.springframework.org/schema/beans"
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xmlns:mybatis="http://mybatis.org/schema/mybatis-spring"
  xsi:schemaLocation="
  http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
  http://mybatis.org/schema/mybatis-spring http://mybatis.org/schema/mybatis-spring.xsd">

  <mybatis:scan base-package="org.mybatis.spring.sample.mapper" />

</beans>

<!-- 方案3 -->
@Configuration
@MapperScan("org.mybatis.spring.sample.mapper")
public class AppConfig {
  // ...
}      

Mybatis与springBoot

​​最简单的SpringBoot整合MyBatis教程_江南一点雨的专栏-CSDN博客_springboot整合mybatis步骤​​

Mybatis与mybatis-plus