文章目录
- 1、前言
- 2、项目搭建
- 2.1 springboot搭建
- 2.2 集成mybatis
- 2.3 集成tk.mapper
- 2.4 集成postgreSQL
- 2.5 集成pagehelper
- 2.6 添加库表
- 3、项目配置
- 3.1 生成实体文件
- 3.2 类型转换器
- 3.2.1 数组类型转换器
- 3.2.2 实体类型转换器
- 3.2.3 实体数组类型转换器
- 3.3 返回主键配置
- 3.2 复杂类型返回
- 4、测试
- 4.1 增加
- 4.2 修改
- 4.3 查询
- 4.4 删除
- 5、大功告成!!!
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、大功告成!!!