问题说明:在我们实际的业务开发中,会有将Java中的集合数据存入数据库中,或者将数据库中的数据读取到List集合中返回的情况,例如,我们需要保存某个用户的爱好,按照我们以往的做法,我们可能在数据库定义hobbies字段使用varchar类型,在Java实体中也使用String类型,在业务代码中将集合数据按照一定的规则转为String字符串再来进行存储,获取的时候在将获取的字符串按之前的规则转为List集合返回,这样做也可以实现我们的需求,但是就代码的冗余和观赏性方面来说是不及我们接下来讲的来实现mytais提供的类型转换接口TypeHandler,自定义类型转换器来的更加优雅。

1、数据库创建用户表

集合存入redis 集合存入数据库_java


其中hobbies列用于存储用户的爱好信息,在实际中这是一个列表信息

2、创建用户实体

package com.lzycug.blog.pojo;

import java.util.List;

/**
 * @author: lzyCug
 * @date: 2021/3/8 11:15
 * @description: 用户实体类
 */
public class User {
    private Integer id;
    private String name;
    private Integer age;
    private String address;
    private List<String> hobbies; // 用户的爱好信息使用List集合来存储

    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;
    }

    public Integer getAge() {
        return age;
    }

    public void setAge(Integer age) {
        this.age = age;
    }

    public String getAddress() {
        return address;
    }

    public void setAddress(String address) {
        this.address = address;
    }

    public List<String> getHobbies() {
        return hobbies;
    }

    public void setHobbies(List<String> hobbies) {
        this.hobbies = hobbies;
    }

    @Override
    public String toString() {
        return "User{" +
                "id=" + id +
                ", name='" + name + '\'' +
                ", age=" + age +
                ", address='" + address + '\'' +
                ", hobbies=" + hobbies +
                '}';
    }
}

3、创建持久层接口及对应的Mapper映射文件

package com.lzycug.blog.dao;

import com.lzycug.blog.pojo.User;
import org.springframework.stereotype.Repository;

import java.util.List;

/**
 * @author: lzyCug
 * @date: 2021/3/8 11:20
 * @description: 持久层接口
 */
@Repository
public interface UserDao {
    Integer addUser(User user);

    List<User> getAllUser();
}
<?xml version="1.0" encoding="UTF-8" ?><!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd" >
<mapper namespace="com.lzycug.blog.dao.UserDao">
    <resultMap id="userMap" type="com.lzycug.blog.pojo.User">
        <id column="id" property="id"></id>
        <result column="name" property="name"></result>
        <result column="age" property="age"></result>
        <result column="address" property="address"></result>
        <result column="hobbies" property="hobbies"></result>
    </resultMap>

    <insert id="addUser" parameterType="com.lzycug.blog.pojo.User">
        INSERT INTO t_user (name, age, address, hobbies) VALUES (#{name}, #{age}, #{address}, #{hobbies})
    </insert>

    <select id="getAllUser" resultMap="userMap">
        SELECT id, name, age, address, hobbies FROM t_user
    </select>
</mapper>

4、编写测试类

package com.lzycug.blog.test;


import com.lzycug.blog.dao.UserDao;
import com.lzycug.blog.pojo.User;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;

import java.util.ArrayList;
import java.util.List;

/**
 * @author: lzyCug
 * @date: 2021/3/8 11:14
 * @description: 测试类
 */
@RunWith(SpringRunner.class)
@SpringBootTest
public class UserTest {
    @Autowired
    UserDao userDao;

    @Test
    public void testAddUser() {
        User user = new User();
        user.setName("张三");
        user.setAge(18);
        user.setAddress("陕西西安");
        List<String> hobbies = new ArrayList<>();
        hobbies.add("乒乓球");
        hobbies.add("羽毛球");
        hobbies.add("橄榄球");
        user.setHobbies(hobbies);

        Integer row = userDao.addUser(user);
        assert(row == 1);
    }

    @Test
    public void testGetUser() {
        List<User> users = userDao.getAllUser();
        for (User user : users) {
            System.out.println(user);
        }
    }
}

5、启动测试

集合存入redis 集合存入数据库_mybatis_02


直接启动后控制台报错,提示"hobbies"属性没有对应的类型处理器,意思就是框架不知道怎么把java中的List和数据库的varchar怎么对应起来,这时候我们需要自定义类型处理器来告诉框架怎么进行类型的对应转换

6、自定义类型处理器

package com.lzycug.blog.handler;

import org.apache.commons.lang3.StringUtils;
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;

/**
 * @author: lzyCug
 * @date: 2021/3/8 15:24
 * @description: 类型转换器,用于数据库的varchar和Java中List<String>类型的相互转换
 */
@MappedJdbcTypes(JdbcType.VARCHAR)
@MappedTypes(List.class)
public class ListToVarcharTypeHandler implements TypeHandler<List<String>> {
    @Override
    public void setParameter(PreparedStatement preparedStatement, int i, List<String> strings, JdbcType jdbcType) throws SQLException {
        // 遍历List类型的入参,拼装为String类型,使用Statement对象插入数据库
        StringBuffer sb = new StringBuffer();
        for (int j = 0; j < strings.size(); j++) {
            if (j == strings.size() - 1) {
                sb.append(strings.get(j));
            } else {
                sb.append(strings.get(j)).append(",");
            }
        }
        preparedStatement.setString(i, sb.toString());
    }

    @Override
    public List<String> getResult(ResultSet resultSet, String s) throws SQLException {
        // 获取String类型的结果,使用","分割为List后返回
        String resultString = resultSet.getString(s);
        if (StringUtils.isNotEmpty(resultString)) {
            return Arrays.asList(resultString.split(","));
        }
        return null;
    }

    @Override
    public List<String> getResult(ResultSet resultSet, int i) throws SQLException {
        // 获取String类型的结果,使用","分割为List后返回
        String resultString = resultSet.getString(i);
        if (StringUtils.isNotEmpty(resultString)) {
            return Arrays.asList(resultString.split(","));
        }
        return null;
    }

    @Override
    public List<String> getResult(CallableStatement callableStatement, int i) throws SQLException {
        // 获取String类型的结果,使用","分割为List后返回
        String resultString = callableStatement.getString(i);
        if (StringUtils.isNotEmpty(resultString)) {
            return Arrays.asList(resultString.split(","));
        }
        return null;
    }
}

创建转换器类实现mybatis提供的TypeHandler接口,接口的泛型为Java中对应的数据类型,在这里使用List<String>类型,实现接口里面的方法,方法分为两类,其中setParameter()方法为插入数据库时,List数据转存varchar的规则,文中使用遍历List类型的入参,拼装为String类型,使用Statement对象插入数据库的方式来实现;剩余的三个方法均为将查询到的数据库varchar数据存入List集合的规则,文中使用获取String类型的结果,使用","分割为List后返回的规则实现。注意在类型转换器上添加注解

@MappedJdbcTypes(JdbcType.VARCHAR)
@MappedTypes(List.class)

除了这里还要再Mapper文件中做一些修改即指定hobbies字段使用我们刚刚自己实现的类型转换器,修改完成后,最终的Mapper文件内容为:

<?xml version="1.0" encoding="UTF-8" ?><!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd" >
<mapper namespace="com.lzycug.blog.dao.UserDao">
    <resultMap id="userMap" type="com.lzycug.blog.pojo.User">
        <id column="id" property="id"></id>
        <result column="name" property="name"></result>
        <result column="age" property="age"></result>
        <result column="address" property="address"></result>
        <result column="hobbies" property="hobbies" typeHandler="com.lzycug.blog.handler.ListToVarcharTypeHandler"></result>
    </resultMap>

    <insert id="addUser" parameterType="com.lzycug.blog.pojo.User">
        INSERT INTO t_user (name, age, address, hobbies) VALUES (#{name}, #{age}, #{address}, #{hobbies,typeHandler=com.lzycug.blog.handler.ListToVarcharTypeHandler})
    </insert>

    <select id="getAllUser" resultMap="userMap">
        SELECT id, name, age, address, hobbies FROM t_user
    </select>
</mapper>

7、重新测试插入和查询

集合存入redis 集合存入数据库_集合存入redis_03


集合存入redis 集合存入数据库_mybatis_04

至此,我们使用自定义类型处理器,优雅的完成了List集合的数据库存取操作,这样我们在业务处理中,对于这种类型的数据转存就是无感的。