1.TypeHandlers介绍

MyBatis 在设置预处理语句(PreparedStatement)中的参数或从结果集中取出一个值时,都会用类型处理器将获取到的值以合适的方式转换成 Java 类型。下表描述了一些默认的类型处理器。注意:从 3.4.5 开始,MyBatis 默认支持 JSR-310(日期和时间 API)。

类型处理器 Java 类型 JDBC 类型
BooleanTypeHandler java.lang.Boolean
, boolean
数据库兼容的 BOOLEAN
ByteTypeHandler java.lang.Byte
, byte
数据库兼容的 NUMERIC
BYTE
ShortTypeHandler java.lang.Short
, short
数据库兼容的 NUMERIC
SMALLINT
IntegerTypeHandler java.lang.Integer
, int
数据库兼容的 NUMERIC
INTEGER
LongTypeHandler java.lang.Long
, long
数据库兼容的 NUMERIC
BIGINT
FloatTypeHandler java.lang.Float
, float
数据库兼容的 NUMERIC
FLOAT
DoubleTypeHandler java.lang.Double
, double
数据库兼容的 NUMERIC
DOUBLE
BigDecimalTypeHandler java.math.BigDecimal 数据库兼容的 NUMERIC
DECIMAL
StringTypeHandler java.lang.String CHAR
, VARCHAR
ClobReaderTypeHandler java.io.Reader -
ClobTypeHandler java.lang.String CLOB
, LONGVARCHAR
NStringTypeHandler java.lang.String NVARCHAR
, NCHAR
NClobTypeHandler java.lang.String NCLOB
BlobInputStreamTypeHandler java.io.InputStream -
ByteArrayTypeHandler byte[] 数据库兼容的字节流类型
BlobTypeHandler byte[] BLOB
, LONGVARBINARY
DateTypeHandler java.util.Date TIMESTAMP
DateOnlyTypeHandler java.util.Date DATE
TimeOnlyTypeHandler java.util.Date TIME
SqlTimestampTypeHandler java.sql.Timestamp TIMESTAMP
SqlDateTypeHandler java.sql.Date DATE
SqlTimeTypeHandler java.sql.Time TIME
ObjectTypeHandler Any OTHER
或未指定类型
EnumTypeHandler Enumeration Type VARCHAR 或任何兼容的字符串类型,用来存储枚举的名称(而不是索引序数值)
EnumOrdinalTypeHandler Enumeration Type 任何兼容的 NUMERIC
DOUBLE
类型,用来存储枚举的序数值(而不是名称)。
SqlxmlTypeHandler java.lang.String SQLXML
InstantTypeHandler java.time.Instant TIMESTAMP
LocalDateTimeTypeHandler java.time.LocalDateTime TIMESTAMP
LocalDateTypeHandler java.time.LocalDate DATE
LocalTimeTypeHandler java.time.LocalTime TIME
OffsetDateTimeTypeHandler java.time.OffsetDateTime TIMESTAMP
OffsetTimeTypeHandler java.time.OffsetTime TIME
ZonedDateTimeTypeHandler java.time.ZonedDateTime TIMESTAMP
YearTypeHandler java.time.Year INTEGER
MonthTypeHandler java.time.Month INTEGER
YearMonthTypeHandler java.time.YearMonth VARCHAR
LONGVARCHAR
JapaneseDateTypeHandler java.time.chrono.JapaneseDate DATE

1.1 处理枚举类型

假设现在student表中有一个sex字段,sex为0时表示性别为男,sex为1时表示性别为女,一般我们会用枚举类型来枚举sex的可选值。在Mybatis中若想映射枚举类型 Enum,则需要从 EnumTypeHandler 或者 EnumOrdinalTypeHandler 中选择一个来使用。默认情况下,MyBatis 会利用 EnumTypeHandler 来把 Enum 值转换成对应的名字。

注意:**EnumTypeHandler** 在某种意义上来说是比较特别的,其它的处理器只针对某个特定的类,而它不同,它会处理任意继承了 **Enum** 的类。
不过,我们可能不想存储名字,相反我们的 DBA 会坚持使用整形值代码。那也一样简单:在配置文件中把 EnumOrdinalTypeHandler 加到 typeHandlers 中即可, 这样每个 RoundingMode 将通过他们的序数值来映射成对应的整形数值。

SQL语句:

  1. drop table if exists student;
  2. create table student(
  3. sid int primary key not null auto_increment,
  4. stuName varchar(25) not null comment '学生姓名',
  5. stuNo varchar(25) not null comment '学号',
  6. age tinyint not null comment 'age',
  7. sex tinyint not null comment '性别,0男1女',
  8. address varchar(200) comment '地址',
  9. birthday TIMESTAMP comment '生日'
  10. ) engine=INNODB character set=utf8mb4 collate=utf8mb4_general_ci row_format=dynamic comment '学生表';
  11. #插入数据
  12. insert into student(stuName,stuNo,age,sex,address,birthday) values('鲁炎','s1000',20,0,'南城','2020-02-02'),
  13. ('王璐','s1001',21,1,'东城','1998-02-02'),('张德发','s1002',25,0,'北京','1997-09-02'),
  14. ('司马青','s1003',23,0,'上海','2000-09-02'),('欧阳倩','s1004',17,1,'沧澜圣城','2001-09-02');

学生实体类(com.fly.entity.Student.class):

  1. package com.fly.entity;
  2. import com.fly.entityEnum.SexEnum;
  3. import lombok.AllArgsConstructor;
  4. import lombok.Data;
  5. import lombok.NoArgsConstructor;
  6. import lombok.experimental.Accessors;
  7. @Data
  8. @AllArgsConstructor
  9. @NoArgsConstructor
  10. @Accessors(chain = true) //开启链式调用
  11. public class Student {
  12. private Integer id;
  13. private String stuName;
  14. private String stuNo;
  15. private Integer age;
  16. private SexEnum sex;
  17. private String address;
  18. private String birthday;
  19. }

实体枚举类(com.fly.eneityEnum.SexEnum):

package com.fly.entityEnum;

public enum SexEnum {
    man,
    woman;
}

mapper接口(com.fly.mapper.StudentMapper.class):

import org.apache.ibatis.annotations.Param;

import java.util.List;

public interface StudentMapper {
    int addStudent(@Param("student") Student student);
    int delStudentById(@Param("id") int id);
    int upStudent(@Param("student")Student student);
    Student queryStudentById(@Param("id") int id);
    List<Student> queryStudent();
}

mapper xml文件(resources/mapper/studentMapper.xml):

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<!-- namespace(命名空间)用于指定Mapper与javaDao的映射关系 唯一  -->
<mapper namespace="com.fly.mapper.StudentMapper">
    <!-- 设置java实体对象与数据库字段的映射关系 -->
    <resultMap id="studentMap" type="com.fly.entity.Student">
        <id property="id" column="sid" />
        <result property="stuName" column="stuName" />
        <result property="stuNo" column="stuNo" />
        <result property="age" column="age" />
        <result property="sex" column="sex" />
        <result property="address" column="address"/>
        <result property="birthday" column="birthday"/>
    </resultMap>

    <!-- 定义sql片段 如果多个语句有重新的内容,可以把它提取到一个sql标签,然后通过
    <include/>标签的refid属性指定引入sql标签的id即可 -->
    <sql id="student_sql">
        sid,stuName,stuNo,age,sex,address,birthday
    </sql>

    <!--
        insert标签标识是一个插入操作,id="addStudent"表示当前操作与
        com.fly.mapper.StudentMapper的addStudent是对应关系,
        parameterType用于指定addStudent方法中参数的类型。

       sqlSession.insert()传入对象参数并不需要#{参数名.属性名}这种形式访问属性,它会对对象进行处理,使得我们可以直接访问属性,
       例如传入的参数是student,如果要访问stuName属性,写成#{stuName}即可访问
     -->
    <insert id="addStudent" parameterType="com.fly.entity.Student">
        insert into student(stuName,stuNo,age,sex,address,birthday) values(#{stuName},
        #{stuNo},#{age},#{sex},#{address},#{birthday});
    </insert>

    <!--由于delStudentById方法传入一个int 类型的参数,所以parameterType的值为java.lang.Integer -->
    <delete id="delStudentById"  parameterType="java.lang.Integer">
        delete from student where  sid=#{id}
    </delete>

    <update id="upStudent" parameterType="com.fly.entity.Student">
        update student set stuName=#{stuName} where sid=#{id}
    </update>

    <!--
        parameterType用于设置对应DAO类中方法的参数类型
        resultType用于设置对饮DAO类中方法的返回值类型,insert、delete、update标签都不用写resultType属性,
        默认返回int类型
    -->
    <select id="queryStudentById" parameterType="java.lang.Integer" resultType="com.fly.entity.Student">
        select <include refid="student_sql"/> from student where sid=#{id};
    </select>

    <select id="queryStudent" resultMap="studentMap">
            select <include refid="student_sql"/> from student;
    </select>
</mapper>

mybatis.xml(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>

    <!-- 类型处理器配置 -->
    <typeHandlers>
          <!-- 对于枚举类型Mybatis默认使用org.apache.ibatis.type.EnumTypeHandler
                          把Enum值转换成对应的名字。

                         如果数据库的字段是以整数类型时,我们在枚举时handler需要设置为
                         org.apache.ibatis.type.EnumOrdinalTypeHandler,这样可以将每个枚举值的序数值(下标)
                       都映射成对应的整形数值。例如:SexEnum枚举类的man属性会被映射为0,因为man属性的下标值为0
                -->
        <typeHandler handler="org.apache.ibatis.type.EnumTypeHandler"
                     javaType="com.fly.entityEnum.SexEnum" />
    </typeHandlers>

    <!-- environments用于指定运行环境配置,default="development"指定运行环境为开发环境 -->
    <environments default="development">
        <environment id="development">
            <!-- 配置事务管理器 类型为JDBC -->
            <transactionManager type="JDBC"/>
            <!-- 配置数据源 数据源类型为POOLED  -->
            <dataSource type="POOLED">
                <property name="driver" value="com.mysql.cj.jdbc.Driver"/>
                <property name="url" value="jdbc:mysql://localhost:3306/test?useUnicode=true&amp;characterEncoding=utf8&amp;serverTimezone=UTC&amp;allowMultiQueries=true"/>
                <property name="username" value="root"/>
                <property name="password" value="123456"/>
            </dataSource>
        </environment>
    </environments>
    <!-- mappers指定一组mapper(映射器) -->
    <mappers>
        <!-- mapper的XML映射文件包含了SQL代码和映射定义信息,resource指定mapper文件的地址,
        待会要在src/main/resources目录下创建一个mapper文件夹,
        然后在mapper文件夹创建一个名为studentMapper.xml的mapper映射文件
         -->
        <mapper resource="mapper/studentMapper.xml"/>
    </mappers>
</configuration>

测试类:

package com.fly;

import com.fly.entity.Student;
import com.fly.entityEnum.SexEnum;
import com.fly.mapper.StudentMapper;
import org.apache.ibatis.datasource.pooled.PooledDataSource;
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.mapping.Environment;
import org.apache.ibatis.session.Configuration;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
import org.apache.ibatis.transaction.TransactionFactory;
import org.apache.ibatis.transaction.jdbc.JdbcTransactionFactory;
import org.junit.BeforeClass;
import org.junit.Test;

import javax.sql.DataSource;
import java.io.InputStream;
import java.util.List;


/**
 * 基于xml形式构建SqlSessionFactory
 */

public class Test01 {

    private static SqlSessionFactory sqlSessionFactory;
    private static SqlSession sqlSession;


    /*通过xml构建sqlSessionFactory*/
    //@BeforeClass表示针对所有测试,只执行一次,且必须为static void
    @BeforeClass
    public static void init() {
        try {
            InputStream stream = Resources.getResourceAsStream("mybatis.xml");
            //SqlSessionFactoryBuilder通过获取配置文件信息得到SqlSessionFactory,看到build()就想到了建造者模式
            sqlSessionFactory = new SqlSessionFactoryBuilder().build(stream);
            sqlSession = sqlSessionFactory.openSession();
            stream.close();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /*通过编写java配置构建sqlSessionFactory*/
    @Test
    public void initConfig() {

        DataSource dataSource = new PooledDataSource("com.mysql.cj.jdbc.Driver",
                "jdbc:mysql://localhost:3306/test?useUnicode=true&characterEncoding=utf8&serverTimezone=UTC&allowMultiQueries=true",
                "root", "123456");
        TransactionFactory transactionFactory = new JdbcTransactionFactory();
        Environment environment = new Environment("development", transactionFactory, dataSource);
        Configuration configuration = new Configuration(environment);
        configuration.setMapUnderscoreToCamelCase(true);
        //设置Mapper
        configuration.addMapper(StudentMapper.class);

        sqlSessionFactory = new SqlSessionFactoryBuilder().build(configuration);

        sqlSession = sqlSessionFactory.openSession();

        /*
         * 如果出现Cause: java.lang.IllegalArgumentException: Mapped Statements collection does not
         * contain value for com.fly.mapper.StudentMapper.queryStudentById 异常是idea的原因,idea默认不是编译java文件夹下的.xml文件,
         * 如果想测试的话,直接把studentMapper.xml复制到target/classes/com/fly/mapper目录下即可
         * */
        System.out.println(sqlSession.selectList("com.fly.mapper.StudentMapper.queryStudentById", 4));
    }


    @Test
    public void addStudent() {
        Student student = new Student();
        student.setStuName("张三");
        student.setStuNo("s10001");
        student.setAge(20);
        student.setSex(SexEnum.woman);
        student.setAddress("天空城");
        student.setBirthday("2020-01-01");
        //表示调用Mapper XML文件中insert标签的id为addStudent的语句并传入student参数执行
        int row = sqlSession.insert("addStudent", student);
        //提交成功数据库才有记录
        sqlSession.commit();
        System.out.println(row > 0 ? "添加成功!" : "添加失败!");
        Test01.close();
    }

    @Test
    public void delStudentById() {
        int row = sqlSession.delete("delStudentById", 9);
        //提交成功数据库才有记录
        sqlSession.commit();
        System.out.println(row > 0 ? "删除成功!" : "删除失败!");
        Test01.close();
    }

    @Test
    public void upStudent() {
        Student student = new Student();
        student.setId(1).setStuName("mybatis");
        int row = sqlSession.update("upStudent", student);
        //提交成功数据库才有记录
        sqlSession.commit();
        System.out.println(row > 0 ? "修改成功!" : "修改失败!");
        Test01.close();

    }

    @Test
    public void queryStudentById() {
        Student student = (Student) sqlSession.selectOne("com.fly.mapper.StudentMapper.queryStudentById", 4);
        System.out.println(student);
        Test01.close();
    }

    @Test
    public void queryStudent() {
        //selectList中填的是mapper中的方法
        List<Object> selectList = sqlSession.selectList("com.fly.mapper.StudentMapper.queryStudent");
        Test01.close();
    }

    public static void close() {
        try {

        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            sqlSession.close();
        }
    }
}

当执行测试类的addStudent方法时会控制台会打印如下异常:

Cause: java.sql.SQLException: Incorrect integer value: 'woman' for column 'sex' at row 1

这是因为student表的sex字段是tinyint类型(小整形类型),调用addStudent()时错把’woman’当做sex的值传入,从而导致传入的参数类型跟数据库字段类型不一致。这是因为sex被设置为枚举类型,在Mybatis的全局配置文件中对于枚举类型我们指定的类型处理器是org.apache.ibatis.type.EnumTypeHandler,此类型处理器会把枚举值转换为对应的名字,例如SexEnum类的woman会被org.apache.ibatis.type.EnumTypeHandler处理为’woman’。

如果想解决枚举类的值映射成数值类型,只需将org.apache.ibatis.type.EnumTypeHandler 替换成org.apache.ibatis.type.EnumOrdinalTypeHandler即可。org.apache.ibatis.type.EnumOrdinalTypeHandler在处理枚举类型时会根据枚举值的序数值(枚举值的下标)映射成对应整形数值。改动mybatis.xml为如下:

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

    <!-- 类型处理器配置 -->
    <typeHandlers>
        <typeHandler handler="org.apache.ibatis.type.EnumOrdinalTypeHandler"
                     javaType="com.fly.entityEnum.SexEnum" />
    </typeHandlers>

    <!-- environments用于指定运行环境配置,default="development"指定运行环境为开发环境 -->
    <environments default="development">
        <environment id="development">
            <!-- 配置事务管理器 类型为JDBC -->
            <transactionManager type="JDBC"/>
            <!-- 配置数据源 数据源类型为POOLED  -->
            <dataSource type="POOLED">
                <property name="driver" value="com.mysql.cj.jdbc.Driver"/>
                <property name="url" value="jdbc:mysql://localhost:3306/test?useUnicode=true&amp;characterEncoding=utf8&amp;serverTimezone=UTC&amp;allowMultiQueries=true"/>
                <property name="username" value="root"/>
                <property name="password" value="123456"/>
            </dataSource>
        </environment>
    </environments>
    <!-- mappers指定一组mapper(映射器) -->
    <mappers>
        <!-- mapper的XML映射文件包含了SQL代码和映射定义信息,resource指定mapper文件的地址,
        待会要在src/main/resources目录下创建一个mapper文件夹,
        然后在mapper文件夹创建一个名为studentMapper.xml的mapper映射文件
         -->
        <mapper resource="mapper/studentMapper.xml"/>
    </mappers>
</configuration>

重新执行addStudent(),结果显示添加成功:
Jietu20210118-124503.jpg
查询也是没问题:
kkkkkk.jpg

2.自定义类型处理器

有时我们需要自定义类型处理器去处理一些特殊的情况,例如:假设有一个hobby(爱好)字段,数据库字段类型为varchar(255),它可以存储多个爱好并以逗号分隔,就像 篮球,美女,跑车,运动这种,我们希望:

  • 存储时将前端传递过来的爱好List转为字符串(格式可以自定义)
  • 查询数据时将查询出来的字符串转换为爱好List

mybatis支持两种方式来实现自定义类型处理器:

  • 实现org.apache.ibatis.type.TypeHandler 接口
  • 继承很便利的类 org.apache.ibatis.type.BaseTypeHandler

2.1 实现TypeHandler接口自定义类型处理器

实现TypeHandler接口需要重写TypeHandler4个方法,TypeHandler源码如下,其中setParameter方法用于把 java 对象设置到 PreparedStatement 的参数中,getResult 方法用于从 ResultSet(根据列名或者索引位置获取)或 CallableStatement(根据存储过程获取)中取出数据转换为 java 对象。

package org.apache.ibatis.type;

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

public interface TypeHandler<T> {
    /*
     * setParameter方法用于设置参数。
       PreparedStatement对象(接口)代表的是一个预编译的SQL语句。它由jdbc提供,它继承了Statement接口,
       相比较Statement接口它不仅继承了Statement接口所有功能,另外它还添加了一整套方法,用于设置发送给数据库以取代 IN 参数占位符的值。
       PreparedStatement对比Statement如如下优点:
       (1).提升代码的可读性和可维护性
       (2).PreparedStatement尽最大可能提高性能,预编译的SQL语句有可能被重复调用.所以语句在被DB的编译器编译后的执行代码被缓存下来,
       那么下次调用时只要是相同的预编译语句就不需要编译。
       (3).最重要的一点是极大地提高了安全性。

       var2表示当前处理字段的下标,var3表示外部传入的参数,var4表示参数的JdbcType
     */
    void setParameter(PreparedStatement var1, int var2, T var3, JdbcType var4) throws SQLException;

    T getResult(ResultSet var1, String var2) throws SQLException;

    T getResult(ResultSet var1, int var2) throws SQLException;

    T getResult(CallableStatement var1, int var2) throws SQLException;
}

SQL语句:

drop table if exists student;
create table student(
sid int primary key not null auto_increment,
stuName varchar(25) not null comment '学生姓名',
stuNo varchar(25) not null comment '学号',
age tinyint not null comment 'age',
sex tinyint not null comment '性别,0男1女',
address varchar(200) comment '地址',
birthday TIMESTAMP comment '生日',
hobby varchar(255) comment '爱好'
)  engine=INNODB character set=utf8mb4 collate=utf8mb4_general_ci row_format=dynamic comment '学生表';


insert into student(stuName,stuNo,age,sex,address,birthday,hobby) 
values('鲁炎','s1000',20,0,'南城','2020-02-02',null),
('王璐','s1001',21,1,'东城','1998-02-02',null),
('张德发','s1002',25,0,'北京','1997-09-02',null),
('司马青','s1003',23,0,'上海','2000-09-02',null),
('欧阳倩','s1004',17,1,'沧澜圣城','2001-09-02',null);

性别枚举类(com.fly.entityEnum):

package com.fly.entityEnum;

public enum SexEnum {
    man(0,"男"),
    woman(1,"女");
    private Integer id;
    private String name;

    SexEnum(Integer id, String name) {
        this.id = id;
        this.name = name;
    }

    public Integer getId() {
        return id;
    }

    public void setId(Integer id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
}

学生实体类(com.fly.entity):

package com.fly.entity;
import com.fly.entityEnum.SexEnum;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.experimental.Accessors;
import java.util.List;
@Data
@AllArgsConstructor
@NoArgsConstructor
@Accessors(chain = true) //开启链式调用
public class Student {
    private Integer id;
    private String stuName;
    private String stuNo;
    private Integer age;
    private SexEnum sex;
    private String address;
    private String birthday;
    private List<String> hobby;
}

自定义类型处理器用于存储List转String,查询时String转换List。

package com.fly.typeHandler;

import org.apache.ibatis.type.JdbcType;
import org.apache.ibatis.type.MappedJdbcTypes;
import org.apache.ibatis.type.MappedTypes;
import org.apache.ibatis.type.TypeHandler;

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

/**
 * MappedJdbcTypes注解用于指定要处理的jdbc数据类型,由于是处理数据库字段类型为varchar,
 * 所以对应的注解值为JdbcType.VARCHAR。如果想设置多个数据库字段类型只需在MappedJdbcTypes注解
 * 的value属性添加多个类型即可
 *
 */
@MappedJdbcTypes(value ={
        JdbcType.VARCHAR,
        JdbcType.CHAR
})
//@MappedTypes指定要处理的java类型
@MappedTypes(List.class)
public class ListConvertVarcharHandler implements TypeHandler<List<String>> {

    /**
     * setParameter用于设置参数
     * @param preparedStatement
     * @param i
     * @param stringList
     * @param jdbcType
     * @throws SQLException
     */
    @Override
    public void setParameter(PreparedStatement preparedStatement, int i, List<String> stringList, JdbcType jdbcType) throws SQLException {
        System.out.println("stringList:"+stringList);
        StringBuffer sb=new StringBuffer();
        for(String str:stringList){
            sb.append(str).append(",");
        }
        preparedStatement.setString(i,sb.deleteCharAt(sb.length()-1).toString());
    }

    @Override
    public List<String> getResult(ResultSet resultSet, String s) throws SQLException {
        String string = resultSet.getString(s);
        if(string!=null){
            return Arrays.asList(string.split(","));
        }
        return null;
    }

    @Override
    public List<String> getResult(ResultSet resultSet, int i) throws SQLException {
        String string = resultSet.getString(i);
        if(string!=null){
            return Arrays.asList(string.split(","));
        }
        return null;
    }

    @Override
    public List<String> getResult(CallableStatement callableStatement, int i) throws SQLException {
        String string = callableStatement.getString(i);
        if (string!=null){
            return Arrays.asList(string.split(","));
        }
        return null;
    }
}

学生Mapper接口:

package com.fly.mapper;

import com.fly.entity.Student;
import org.apache.ibatis.annotations.Param;

import java.util.List;

public interface StudentMapper {
    int addStudent(@Param("student") Student student);
    int delStudentById(@Param("id") int id);
    int upStudent(@Param("student")Student student);
    Student queryStudentById(@Param("id") int id);
    List<Student> queryStudent();
}

学生mapper映射文件(resources/mapper/studentMapper.xml):

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<!-- namespace(命名空间)用于指定Mapper与javaDao的映射关系 唯一  -->
<mapper namespace="com.fly.mapper.StudentMapper">
    <!-- 设置java实体对象与数据库字段的映射关系 -->
    <resultMap id="studentMap" type="com.fly.entity.Student">
        <id property="id" column="sid" />
        <result property="stuName" column="stuName" />
        <result property="stuNo" column="stuNo" />
        <result property="age" column="age" />
        <result property="sex" column="sex" />
        <result property="address" column="address"/>
        <result property="birthday" column="birthday"/>
        <result property="hobby" column="hobby"
            typeHandler="com.fly.typeHandler.ListConvertVarcharHandler"/>
    </resultMap>

    <!-- 定义sql片段 如果多个语句有重新的内容,可以把它提取到一个sql标签,然后通过
    <include/>标签的refid属性指定引入sql标签的id即可 -->
    <sql id="student_sql">
        sid,stuName,stuNo,age,sex,address,birthday,hobby
    </sql>

    <!--
        insert标签标识是一个插入操作,id="addStudent"表示当前操作与
        com.fly.mapper.StudentMapper的addStudent是对应关系,
        parameterType用于指定addStudent方法中参数的类型。

       sqlSession.insert()传入对象参数并不需要#{参数名.属性名}这种形式访问属性,它会对对象进行处理,使得我们可以直接访问属性,
       例如传入的参数是student,如果要访问stuName属性,写成#{stuName}即可访问
     -->
    <insert id="addStudent" parameterType="com.fly.entity.Student">
        insert into student(stuName,stuNo,age,sex,address,birthday,hobby)
        values(#{stuName},#{stuNo},#{age},#{sex},#{address},#{birthday},
        #{hobby,typeHandler=com.fly.typeHandler.ListConvertVarcharHandler});
    </insert>

    <!--由于delStudentById方法传入一个int 类型的参数,所以parameterType的值为java.lang.Integer -->
    <delete id="delStudentById"  parameterType="java.lang.Integer">
        delete from student where  sid=#{id}
    </delete>

    <update id="upStudent" parameterType="com.fly.entity.Student">
        update student set stuName=#{stuName} where sid=#{id}
    </update>

    <!--
        parameterType用于设置对应DAO类中方法的参数类型
        resultType用于设置对饮DAO类中方法的返回值类型,insert、delete、update标签都不用写resultType属性,
        默认返回int类型
    -->
    <select id="queryStudentById" parameterType="java.lang.Integer" resultType="com.fly.entity.Student">
        select <include refid="student_sql"/> from student where sid=#{id};
    </select>

    <select id="queryStudent" resultMap="studentMap">
            select <include refid="student_sql"/> from student;
    </select>
</mapper>

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>

    <!-- 类型处理器配置 -->
    <typeHandlers>
        <typeHandler handler="org.apache.ibatis.type.EnumOrdinalTypeHandler"
                     javaType="com.fly.entityEnum.SexEnum" />
    </typeHandlers>

    <!-- environments用于指定运行环境配置,default="development"指定运行环境为开发环境 -->
    <environments default="development">
        <environment id="development">
            <!-- 配置事务管理器 类型为JDBC -->
            <transactionManager type="JDBC"/>
            <!-- 配置数据源 数据源类型为POOLED  -->
            <dataSource type="POOLED">
                <property name="driver" value="com.mysql.cj.jdbc.Driver"/>
                <property name="url" value="jdbc:mysql://localhost:3306/test?useUnicode=true&amp;characterEncoding=utf8&amp;serverTimezone=UTC&amp;allowMultiQueries=true"/>
                <property name="username" value="root"/>
                <property name="password" value="123456"/>
            </dataSource>
        </environment>
    </environments>
    <!-- mappers指定一组mapper(映射器) -->
    <mappers>
        <!-- mapper的XML映射文件包含了SQL代码和映射定义信息,resource指定mapper文件的地址,
        待会要在src/main/resources目录下创建一个mapper文件夹,
        然后在mapper文件夹创建一个名为studentMapper.xml的mapper映射文件
         -->
        <mapper resource="mapper/studentMapper.xml"/>
    </mappers>
</configuration>

测试类:

package com.fly;

import com.fly.entity.Student;
import com.fly.entityEnum.SexEnum;
import com.fly.mapper.StudentMapper;
import org.apache.ibatis.datasource.pooled.PooledDataSource;
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.mapping.Environment;
import org.apache.ibatis.session.Configuration;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
import org.apache.ibatis.transaction.TransactionFactory;
import org.apache.ibatis.transaction.jdbc.JdbcTransactionFactory;
import org.junit.BeforeClass;
import org.junit.Test;

import javax.sql.DataSource;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.List;


/**
 * 基于xml形式构建SqlSessionFactory
 */

public class Test01 {

    private static SqlSessionFactory sqlSessionFactory;
    private static SqlSession sqlSession;


    /*通过xml构建sqlSessionFactory*/
    //@BeforeClass表示针对所有测试,只执行一次,且必须为static void
    @BeforeClass
    public static void init() {
        try {
            InputStream stream = Resources.getResourceAsStream("mybatis.xml");
            //SqlSessionFactoryBuilder通过获取配置文件信息得到SqlSessionFactory,看到build()就想到了建造者模式
            sqlSessionFactory = new SqlSessionFactoryBuilder().build(stream);
            sqlSession = sqlSessionFactory.openSession();
            stream.close();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /*通过编写java配置构建sqlSessionFactory*/
    @Test
    public void initConfig() {

        DataSource dataSource = new PooledDataSource("com.mysql.cj.jdbc.Driver",
                "jdbc:mysql://localhost:3306/test?useUnicode=true&characterEncoding=utf8&serverTimezone=UTC&allowMultiQueries=true",
                "root", "123456");
        TransactionFactory transactionFactory = new JdbcTransactionFactory();
        Environment environment = new Environment("development", transactionFactory, dataSource);
        Configuration configuration = new Configuration(environment);
        configuration.setMapUnderscoreToCamelCase(true);
        //设置Mapper
        configuration.addMapper(StudentMapper.class);

        sqlSessionFactory = new SqlSessionFactoryBuilder().build(configuration);

        sqlSession = sqlSessionFactory.openSession();

        /*
         * 如果出现Cause: java.lang.IllegalArgumentException: Mapped Statements collection does not
         * contain value for com.fly.mapper.StudentMapper.queryStudentById 异常是idea的原因,idea默认不是编译java文件夹下的.xml文件,
         * 如果想测试的话,直接把studentMapper.xml复制到target/classes/com/fly/mapper目录下即可
         * */
        System.out.println(sqlSession.selectList("com.fly.mapper.StudentMapper.queryStudentById", 4));
    }


    @Test
    public void addStudent() {
        Student student = new Student();
        student.setStuName("张三");
        student.setStuNo("s10001");
        student.setAge(20);
        student.setSex(SexEnum.man);
        student.setAddress("天空城");
        student.setBirthday("2020-01-01");
        /**
         * 测试自定义类型处理器
         */
        List<String> hobbyList = new ArrayList<String>();
        hobbyList.add("女人");
        hobbyList.add("人民币");
        hobbyList.add("超能力");
        student.setHobby(hobbyList);

        //表示调用Mapper XML文件中insert标签的id为addStudent的语句并传入student参数执行
        int row = sqlSession.insert("addStudent", student);
        //提交成功数据库才有记录
        sqlSession.commit();
        System.out.println(row > 0 ? "添加成功!" : "添加失败!");
        Test01.close();
    }

    @Test
    public void delStudentById() {
        int row = sqlSession.delete("delStudentById", 9);
        //提交成功数据库才有记录
        sqlSession.commit();
        System.out.println(row > 0 ? "删除成功!" : "删除失败!");
        Test01.close();
    }

    @Test
    public void upStudent() {
        Student student = new Student();
        student.setId(1).setStuName("mybatis");
        int row = sqlSession.update("upStudent", student);
        //提交成功数据库才有记录
        sqlSession.commit();
        System.out.println(row > 0 ? "修改成功!" : "修改失败!");
        Test01.close();

    }

    @Test
    public void queryStudentById() {
        Student student = (Student) sqlSession.selectOne("com.fly.mapper.StudentMapper.queryStudentById", 15);
        System.out.println("queryStudentById:"+student.getSex());
        Test01.close();
    }

    @Test
    public void queryStudent() {
        //selectList中填的是mapper中的方法
        List<Student> selectList = sqlSession.selectList("com.fly.mapper.StudentMapper.queryStudent");
        for (Student student:selectList){
            if(student.getHobby()!=null){
              System.out.println("Hobby:"+student.getHobby());
            }
            System.out.println("Sex:"+student.getSex().getId());
        }
        Test01.close();
    }

    public static void close() {
        try {

        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            sqlSession.close();
        }
    }
}

测试添加学生:
pppppp11111.jpg
测试查询学生列表:
ppppp22222.jpg
查询数据库结果:
pppppp3333.jpg

2.2 继承BaseTypeHandler类型自定义类型处理器

继承BaseTypeHandler类跟实现TypeHandler的做法是类似的,BaseTypeHandler类实现了TypeHandler接口。
com.fly.typeHandler.ListToVarcharHandler.class:

package com.fly.typeHandler;

import org.apache.ibatis.type.BaseTypeHandler;
import org.apache.ibatis.type.JdbcType;
import org.apache.ibatis.type.MappedJdbcTypes;
import org.apache.ibatis.type.MappedTypes;

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

/**
 * 自定义类型处理器方式2继承
 */
@MappedJdbcTypes(JdbcType.VARCHAR)
@MappedTypes(List.class)
public class ListToVarcharHandler extends BaseTypeHandler<List<String>> {

    @Override
    public void setNonNullParameter(PreparedStatement ps, int i, List<String> strings, JdbcType jdbcType) throws SQLException {
        StringBuffer sb=new StringBuffer();
        for(String string:strings){
            sb.append(string).append(",");
        }
        ps.setString(i,sb.deleteCharAt(sb.length()-1).toString());
    }

    @Override
    public List<String> getNullableResult(ResultSet resultSet, String s) throws SQLException {
        String string = resultSet.getString(s);
        if(string!=null){
            return Arrays.asList(string.split(","));
        }
        return null;
    }

    @Override
    public List<String> getNullableResult(ResultSet resultSet, int i) throws SQLException {
        String string = resultSet.getString(i);
        if(string!=null){
            return Arrays.asList(string.split(","));
        }
        return null;
    }

    @Override
    public List<String> getNullableResult(CallableStatement callableStatement, int i) throws SQLException {
        String string = callableStatement.getString(i);
        if(string!=null){
            return Arrays.asList(string.split(","));
        }
        return null;
    }
}

修改mapper使用ListToVarcharHandler类型处理器:

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<!-- namespace(命名空间)用于指定Mapper与javaDao的映射关系 唯一  -->
<mapper namespace="com.fly.mapper.StudentMapper">
    <!-- 设置java实体对象与数据库字段的映射关系 -->
    <resultMap id="studentMap" type="com.fly.entity.Student">
        <id property="id" column="sid" />
        <result property="stuName" column="stuName" />
        <result property="stuNo" column="stuNo" />
        <result property="age" column="age" />
        <result property="sex" column="sex" />
        <result property="address" column="address"/>
        <result property="birthday" column="birthday"/>
        <result property="hobby" column="hobby"
            typeHandler="com.fly.typeHandler.ListToVarcharHandler"/>
    </resultMap>

    <!-- 定义sql片段 如果多个语句有重新的内容,可以把它提取到一个sql标签,然后通过
    <include/>标签的refid属性指定引入sql标签的id即可 -->
    <sql id="student_sql">
        sid,stuName,stuNo,age,sex,address,birthday,hobby
    </sql>

    <!--
        insert标签标识是一个插入操作,id="addStudent"表示当前操作与
        com.fly.mapper.StudentMapper的addStudent是对应关系,
        parameterType用于指定addStudent方法中参数的类型。

       sqlSession.insert()传入对象参数并不需要#{参数名.属性名}这种形式访问属性,它会对对象进行处理,使得我们可以直接访问属性,
       例如传入的参数是student,如果要访问stuName属性,写成#{stuName}即可访问
     -->
    <insert id="addStudent" parameterType="com.fly.entity.Student">
        insert into student(stuName,stuNo,age,sex,address,birthday,hobby)
        values(#{stuName},#{stuNo},#{age},#{sex},#{address},#{birthday},
        #{hobby,typeHandler=com.fly.typeHandler.ListConvertVarcharHandler});
    </insert>

    <!--由于delStudentById方法传入一个int 类型的参数,所以parameterType的值为java.lang.Integer -->
    <delete id="delStudentById"  parameterType="java.lang.Integer">
        delete from student where  sid=#{id}
    </delete>

    <update id="upStudent" parameterType="com.fly.entity.Student">
        update student set stuName=#{stuName} where sid=#{id}
    </update>

    <!--
        parameterType用于设置对应DAO类中方法的参数类型
        resultType用于设置对饮DAO类中方法的返回值类型,insert、delete、update标签都不用写resultType属性,
        默认返回int类型
    -->
    <select id="queryStudentById" parameterType="java.lang.Integer" resultType="com.fly.entity.Student">
        select <include refid="student_sql"/> from student where sid=#{id};
    </select>

    <select id="queryStudent" resultMap="studentMap">
            select <include refid="student_sql"/> from student;
    </select>
</mapper>

再测试添加学生和查询学生List结果跟实现TypeHandler接口的例子是一样的。