springboot+mybatis+tkmybatis+postgresSQL实现主键返回,自定义handler处理数组和jsonb

1、前言

公司的一个项目是用springdata jpa实现的,但是公司中没人对DDD有深入的了解,导致在开发过过程中大家都是面向数据库开发的,而通过jpa自动生成的表存才大量的外键(删除后也会自动添加上), 导致大家在开发的过程中很是痛苦,所以就有了重构的项目,经过几天的时间还真让我自己给重构成功了,这里记录下在重构过程中遇到的问题,希望对大家有所帮助!

2、项目搭建

2.1 springboot搭建

springboot的搭建可以参考博主的另外一篇文章 idea创建Springboot 2.X项目

2.2 集成mybatis

  • 引入依赖坐标
<!-- mybatis -->
<dependency>
  <groupId>org.mybatis.spring.boot</groupId>
  <artifactId>mybatis-spring-boot-starter</artifactId>
  <version>1.3.1</version>
</dependency>
  • 在application.yml或者application.propertiesz添加如下配置
#======================mybatis配置============================
mybatis:
  configuration:
    # 返回主键
    use-generated-keys: true
    # 开启驼峰功能
    map-underscore-to-camel-case: true
  type-aliases-package: com.chilx.entity
  # mapper的位置
  mapper-locations: classpath*:com/chilx/**/*.xml
  • 修改pom文件添加加载xml的配置(很多人忘记这一步

我把mapper.xml放到了src/main/java包下所以要配置sources路径,否则回报xml找到不到的错误

<resources>
  <!-- 打包.xml文件 -->
  <resource>
    <directory>src/main/java</directory>
    <includes>
      <include>**/*.xml</include>
    </includes>
  </resource>
  <resource>
    <directory>src/main/resources</directory>
    <includes>
      <include>**/*.*</include>
    </includes>
  </resource>
</resources>

2.3 集成tk.mapper

  • 引入坐标依赖
<!-- mapper springboot专用的stater -->
<dependency>
  <groupId>tk.mybatis</groupId>
  <artifactId>mapper-spring-boot-starter</artifactId>
  <version>1.2.3</version>
</dependency>
  • 在application.yml或者application.propertiesz添加如下配置
#======================mapper配置============================
mapper:
  mappers:
    - com.chilx.common.BaseInfoMapper
  # 对于一般的getAllIfColumnNode,是否判断!='',默认不判断
  not-empty: false
  # 获取主键自增回写SQL
  IDENTITY: SELECT nextval({0})
  # 指定序列格式化方式 nextval({0}) 获取下一个id
  # 当实体类指定序列是必须开启此配置
  seq-format: nextval({0})
  # 写入之前
  before: true
  # 是否只处理基本类型: 当为true时才会处理arry和jsonb处理复杂类型
  use-simple-type: false
  • 最后在启动类上扫描mapper

    注意: 引用的是tk.mybatis.spring.annotation.MapperScan而不是org.mybatis.spring.annotation.MapperScan

@SpringBootApplication
// 此处配置*mapper.java所在的包路径
@MapperScan(basePackages = "com.chilx.dao")
public class Application {

    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }

}

2.4 集成postgreSQL

  • 引入坐标依赖
<dependency>
  <groupId>org.postgresql</groupId>
  <artifactId>postgresql</artifactId>
  <version>42.1.4</version>
</dependency>
  • 在application.yml或者application.propertiesz添加如下配置
spring:
  #======================数据库配置============================
  datasource:
    url: jdbc:postgresql://localhost:5432/postgres?useSSL=false&stringtype=unspecified
    username: postgres
    password: 123456
    driver-class-name: org.postgresql.Driver

2.5 集成pagehelper

  • 引入坐标依赖
<!-- 分页插件 -->
<dependency>
  <groupId>com.github.pagehelper</groupId>
  <artifactId>pagehelper-spring-boot-starter</artifactId>
  <version>1.2.3</version>
</dependency>
  • 在application.yml或者application.propertiesz添加如下配置

具体参数配置

#======================分页配置============================
pagehelper:
  helperDialect: postgresql
  # 分页合理化参数,默认值为false。
  # 当该参数设置为 true 时,pageNum<=0 时会查询第一页, pageNum>pages(超过总数时),会查询最后一页。
  # 默认false 时,直接根据参数进行查询
  reasonable: true
  supportMethodsArguments: true
  params: count=countSql

2.6 添加库表

添加用户信息表

-- 用户信息表
CREATE TABLE "public"."user_info" (
  "id" serial4 NOT NULL,
  "user_name" varchar(255) COLLATE "pg_catalog"."default",
  "hobbies" varchar[] COLLATE "pg_catalog"."default",
  "hobbies_detail" jsonb,
  "lover" jsonb,
  CONSTRAINT "user_info_pkey" PRIMARY KEY ("id")
)
;

ALTER TABLE "public"."user_info" 
  OWNER TO "postgres";

COMMENT ON COLUMN "public"."user_info"."id" IS '主键';

COMMENT ON COLUMN "public"."user_info"."user_name" IS '姓名';

COMMENT ON COLUMN "public"."user_info"."hobbies" IS '爱好';

COMMENT ON COLUMN "public"."user_info"."hobbies_detail" IS '爱好详情';

COMMENT ON COLUMN "public"."user_info"."lover" IS '喜欢的人';

项目的基本架构到这里算是搭建完成,下面让我们生成对应的实体和mapper文件并进行相应的配置

3、项目配置

3.1 生成实体文件

利用mybatis-generator生成对应的实体和mapper文件并进行修改, 生成过程这里不在赘述网上多的是!

  • 修改生成的实体UserInfo
import com.chilx.handler.ArrayTypeHandler;
import com.chilx.handler.HobbyArrayHandler;
import com.chilx.handler.UserInfoHandler;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import tk.mybatis.mapper.annotation.ColumnType;

import javax.persistence.*;

@Table(name = "public.user_info")
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class UserInfo {
    /**
     * 主键
     * GeneratedValue
     *      strategy: 主键生成策略 这里使用序列;
     *      generator: 对应SequenceGenerator中的name属性,可自定义两者一直即可;
     * SequenceGenerator
     *      name: 对应GeneratedValue中的generator的属性,可自定义两者一直即可;
     *      sequenceName: 生成id的序列, 注意这里需要用单引号引着;
     *      allocationSize: 步增大小
     */
    @Id
    @GeneratedValue(strategy = GenerationType.SEQUENCE, generator="seq")
    @SequenceGenerator(name = "seq", sequenceName = "'user_info_id_seq'", allocationSize = 1)
    private Integer id;

    /**
     * 姓名
     */
    @Column(name = "user_name")
    private String userName;

    /**
     * 爱好
     */
    @Column(name = "hobbies")
    // 配置数组类型转化器
    @ColumnType(column = "hobbies", typeHandler = ArrayTypeHandler.class)
    private String[] hobbies;

    /**
     * 爱好详情
     */
    @Column(name = "hobbies_detail")
    // 配置实体数组类型转化器
    @ColumnType(column = "hobbies_detail", typeHandler = HobbyArrayHandler.class)
    private Hobby[] hobbiesDetail;

    /**
     * 喜欢的人
     */
    @Column(name = "lover")
    // 配置实体类型转化器
    @ColumnType(column = "lover", typeHandler = UserInfoHandler.class)
    private UserInfo lover;
}
  • 修改UserInfo.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" >
<mapper namespace="com.chilx.dao.UserInfoMapper">
    <resultMap id="BaseResultMap" type="com.chilx.entity.UserInfo">
        <id column="id" property="id" jdbcType="INTEGER"/>
        <result column="user_name" property="userName" jdbcType="VARCHAR"/>
        <result column="hobbies" property="hobbies" typeHandler="com.chilx.handler.ArrayTypeHandler"/>
        <result column="hobbies_detail" property="hobbiesDetail" typeHandler="com.chilx.handler.HobbyArrayHandler"/>
        <result column="lover" property="lover" typeHandler="com.chilx.handler.UserInfoHandler"/>
    </resultMap>
</mapper>

3.2 类型转换器

3.2.1 数组类型转换器

import java.sql.*;


/**
 * mybatis 数组类型处理器
 *
 * @author chilx
 * @date 2020/11/20
 **/
@MappedJdbcTypes(JdbcType.ARRAY)
@MappedTypes(String[].class)
public class ArrayTypeHandler extends BaseTypeHandler<Object[]> {

    private static final String TYPE_NAME_VARCHAR = "varchar";
    private static final String TYPE_NAME_INTEGER = "integer";
    private static final String TYPE_NAME_BOOLEAN = "boolean";
    private static final String TYPE_NAME_NUMERIC = "numeric";

    @Override
    public void setNonNullParameter(PreparedStatement ps, int i, Object[] parameter, JdbcType jdbcType) throws SQLException {
        String typeName = null;
        if (parameter instanceof Integer[]) {
            typeName = TYPE_NAME_INTEGER;
        } else if (parameter instanceof String[]) {
            typeName = TYPE_NAME_VARCHAR;
        } else if (parameter instanceof Boolean[]) {
            typeName = TYPE_NAME_BOOLEAN;
        } else if (parameter instanceof Double[]) {
            typeName = TYPE_NAME_NUMERIC;
        }

        if (typeName == null) {
            throw new TypeException("ArrayTypeHandler parameter typeName error, your type is " + parameter.getClass().getName());
        }

        // 这3行是关键的代码,创建Array,然后ps.setArray(i, array)就可以了
        Connection conn = ps.getConnection();
        Array array = conn.createArrayOf(typeName, parameter);
        ps.setArray(i, array);
    }

    @Override
    public Object[] getNullableResult(ResultSet resultSet, String s) throws SQLException {
        return getArray(resultSet.getArray(s));
    }

    @Override
    public Object[] getNullableResult(ResultSet resultSet, int i) throws SQLException {
        return getArray(resultSet.getArray(i));
    }

    @Override
    public Object[] getNullableResult(CallableStatement callableStatement, int i) throws SQLException {
        return getArray(callableStatement.getArray(i));
    }

    private Object[] getArray(Array array) {
        if (array == null) {
            return null;
        }
        try {
            return (Object[]) array.getArray();
        } catch (Exception e) {
        }
        return null;
    }

3.2.2 实体类型转换器

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.chilx.entity.UserInfo;
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 org.postgresql.util.PGobject;

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

/**
 * mybatis JSON类型处理器
 *
 * @author chilx
 * @date 2020/11/20
 **/
@MappedJdbcTypes(JdbcType.OTHER)
@MappedTypes(UserInfo.class)
public class UserInfoHandler extends BaseTypeHandler<UserInfo> {
    private static final PGobject PG_OBJECT = new PGobject();

    @Override
    public void setNonNullParameter(PreparedStatement ps, int i, UserInfo parameter, JdbcType jdbcType)
            throws SQLException {
        if (ps != null) {
            PG_OBJECT.setType("jsonb");
            PG_OBJECT.setValue(JSON.toJSONString(parameter));
            ps.setObject(i, PG_OBJECT);
        }
    }

    @Override
    public UserInfo getNullableResult(ResultSet rs, String columnName) throws SQLException {
        return getArray(rs.getObject(columnName));
    }

    @Override
    public UserInfo getNullableResult(ResultSet rs, int columnIndex) throws SQLException {

        return getArray(rs.getObject(columnIndex));
    }

    @Override
    public UserInfo getNullableResult(CallableStatement cs, int columnIndex) throws SQLException {

        return getArray(cs.getObject(columnIndex));
    }


    private UserInfo getArray(Object obj) {
        if (obj == null) {
            return null;
        }
        try {
            Object value = JSONObject.parseObject(JSON.toJSONString(obj)).get("value");
            return JSON.parseObject(value.toString(), UserInfo.class);
        } catch (Exception ignored) {
        }
        return null;
    }
}

3.2.3 实体数组类型转换器

和实体类型转换是一样的, 只是一个是单个实体一个是数组

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.chilx.entity.Hobby;
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 org.postgresql.util.PGobject;

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

/**
 * mybatis 数组类型处理器
 *
 * @author chilx
 * @date 2020/11/20
 **/
@MappedJdbcTypes(JdbcType.OTHER)
@MappedTypes(Hobby[].class)
public class HobbyArrayHandler extends BaseTypeHandler<Hobby[]> {
    private static final PGobject PG_OBJECT = new PGobject();

    @Override
    public void setNonNullParameter(PreparedStatement ps, int i, Hobby[] parameter, JdbcType jdbcType)
            throws SQLException {
        if (ps != null) {
            PG_OBJECT.setType("jsonb");
            PG_OBJECT.setValue(JSON.toJSONString(parameter));
            ps.setObject(i, PG_OBJECT);
        }
    }

    @Override
    public Hobby[] getNullableResult(ResultSet rs, String columnName) throws SQLException {
        return getArray(rs.getObject(columnName));
    }

    @Override
    public Hobby[] getNullableResult(ResultSet rs, int columnIndex) throws SQLException {

        return getArray(rs.getObject(columnIndex));
    }

    @Override
    public Hobby[] getNullableResult(CallableStatement cs, int columnIndex) throws SQLException {

        return getArray(cs.getObject(columnIndex));
    }


    private Hobby[] getArray(Object obj) {
        if (obj == null) {
            return null;
        }
        try {
            Object value = JSONObject.parseObject(JSON.toJSONString(obj)).get("value");
            return JSON.parseObject(value.toString(), Hobby[].class);
        } catch (Exception ignored) {
        }
        return null;
    }
}

3.3 返回主键配置

  • 配置文件
# mybatis增加以下配置(2.2已配置)
mybatis:
  configuration:
    # 返回主键
    use-generated-keys: true

# mapper增加以下配置(2.3已配置)
mapper:
  # 获取主键自增回写SQL
  IDENTITY: SELECT nextval({0})
  # 指定序列格式化方式 nextval({0}) 获取下一个id
  # 当实体类指定序列是必须开启此配置
  seq-format: nextval({0})
  # 写入之前
  before: true
  # 是否只处理简单类型,
  #  true: 只处理基本数据类型
  #  false: 处理复杂类型,包含自定义handler
  use-simple-type: false

  • 实体类主键添加注解
    /**
     * 主键
     * GeneratedValue
     *      strategy: 主键生成策略 这里使用序列;
     *      generator: 对应SequenceGenerator中的name属性,可自定义两者一直即可;
     * SequenceGenerator
     *      name: 对应GeneratedValue中的generator的属性,可自定义两者一直即可;
     *      sequenceName: 生成id的序列, 注意这里需要用单引号引着;
     *      allocationSize: 步增大小
     */
    @Id
    @GeneratedValue(strategy = GenerationType.SEQUENCE, generator="seq")
    @SequenceGenerator(name = "seq", sequenceName = "'user_info_id_seq'", allocationSize = 1)
    private Integer id;

3.2 复杂类型返回

  • 配置文件
mapper:
  # 是否只处理简单类型,
  #  true: 只处理基本数据类型
  #  false: 处理复杂类型,包含自定义handler
  use-simple-type: false

  • 自定义handler

    详情自定分 — > 3.2 类型转换器

4、测试

4.1 增加

在这里插入图片描述

4.2 修改

在这里插入图片描述

4.3 查询

在这里插入图片描述

4.4 删除

在这里插入图片描述

5、大功告成!!!

附上项目地址:
码云

github

  • 2
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值