盒子
盒子
文章目录
  1. 一、架构说明
  2. 二、第一部分实现
    1. 2.0 实现db.properties文件,以及对应的映射类configuration
    2. 2.1 实现数据库中表对应的Pojo
    3. 2.2 实现连接的提供,datasourse资源文件的映射
    4. 2.3 根据数据库的表结构生成对应的pojo
    5. 2.4 完成Mysql字段类型与java数据类型的转换
      1. 2.4.1 封装mysql字段类型
      2. 2.4.2 实现具体的mysql字段类型与java类型转化
  3. 三、完成javaFileUtils
    1. 3.1 为完成javaFileUtils做出必要的准备
      1. 3.1.1 为更好的解耦,我们抽取公用的方法放到工具类中
      2. 3.1.2 设计一个属性的字段,与set,get方法
    2. 3.2 java的file生成我们在javaFileUtils工具类里面完成,其主要方法如下
  4. 四、完善TableContext

'写出你自己的ORM框架(一) '

前言:读完本片文章大约20分

主要分为如下几个包,和以下几个类,接口(一级为包,二级(普通颜色为类,黄色为接口),加粗为说明)

  • cn.gxm.sorm.bean
    • ColumnInfo 对应数据库中的每一个字段结构
    • TableInfo 对应数据库中的每表结构
    • Configuration 映射资源文件(数据源 ,dataSource,将配置文件中的信息映射成对象)
  • cn.gxm.sorm.core
    • DBManager 根据配置信息,维持连接对象的管理(增加连接池功能)
    • ==Query== 操作数据库的核心接口,CRUD都在这里定义
    • QueryFactory 工厂模式创建核心 query 对象
    • TableContext 理数据库中的所有表和java中的对象的关系可以根据表生成java对象
    • ==TypeConvertor== 用于数据数据类型与java数据类型转换,因为数据库的不同,类型转换也不同,所以定义为接口
  • cn.gxm.sorm.utils
    • JavaFileUtils 操作文件工具类
    • JDBCUtils 封装数据库连接操作工具类
    • ReflectUtils 反射的工具类
    • StringUtils 操作字符串的工具类

二、第一部分实现

2.0 实现db.properties文件,以及对应的映射类configuration

  • db.properties文件即datasourse,连接数据库的必要参数,如下:
    前四个大家都知道,

    • srcPath 是以后使用我们这个框架项目的src路径
    • pojoPackage 是我们根据数据库的表信息生成的pojo的位置
      1
      2
      3
      4
      5
      6
      driver=com.mysql.jdbc.Driver
      url=jdbc:mysql://127.0.0.1:3306/sorm
      username=root
      password=123456
      srcPath=D://IntelliJ_IDEA//MyDemo//SORM//src
      pojoPackage=cn.gxm.sorm.pojo
  • configuration类就不用多说了(set,get等方法省略),如下

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    /**
    * 数据库驱动
    */
    private String driver;

    /**
    * 连接数据库url
    */
    private String url;

    /**
    * 数据库用户名
    */
    private String username;

    /**
    * 数据库密码
    */
    private String password;

    /**
    * 项目src绝对路径
    */
    private String srcPath;

    /**
    * 生成的表对象的包路径
    */
    private String pojoPackage;

2.1 实现数据库中表对应的Pojo

后面我们将会通过数据库的元数据获取表,以及表中的字段,所以,我们需要对应的pojo,如下
字段:(set,get等方法省略)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
/**
* @author GXM www.guokangjie.cn
* @date 2019/5/15
*
* 对应数据库中的每一个字段结构
*/
public class ColumnInfo {

/**
* 字段名称 例如 id,name,age
*/
private String name;

/**
* 字段类型 例如 varchar
*/
private String dataType;

/**
* 字段键类型 例如 1:普通键 2:主键 3:外键
*/
private Integer keyType;

(set,get等方法省略)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
/**
* @author GXM www.guokangjie.cn
* @date 2019/5/15
*
* 对应数据库中的表结构
*/
public class TableInfo {
/**
* 表的名称 例如 User
*/
private String name;

/**
* 表中的字段,使用map为方便后期获取
* 例如 map<id,ColumnInfo>
*/
private Map<String,ColumnInfo> columns;

/**
* 表中的唯一主键
*/
private ColumnInfo onlyPriKey;

/**
* 联合主键即(多个字段组成的主键)
*/
private List<ColumnInfo> unionKey;

2.2 实现连接的提供,datasourse资源文件的映射

从前面的架构来说,连接的提供以及资源文件的映射都在DBManager中处理,因为资源文件映射只需要一次即可,所以放在static中(只会加载一次),并且向外提供了获取连接的静态方法,以及Configuration静态方法,方便外界的操作!

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
package cn.gxm.sorm.core;

import cn.gxm.sorm.bean.Configuration;

import java.io.IOException;
import java.sql.Connection;
import java.sql.DriverManager;
import java.util.Properties;

/**
* @author GXM www.guokangjie.cn
* @date 2019/5/15
*
* 根据配置信息,维持连接对象的管理(增加连接词功能)
*/
public class DBManager {

private static Configuration configuration = null;

private static Properties properties = null;
public DBManager() {
}

// 将数据加载到Configuration类中
static {
try {
properties = new Properties();
configuration = new Configuration();
properties.load(Thread.currentThread().getContextClassLoader().getResourceAsStream("db.properties"));

configuration.setDriver(properties.getProperty("driver"));
configuration.setUrl(properties.getProperty("url"));
configuration.setUsername(properties.getProperty("username"));
configuration.setPassword(properties.getProperty("password"));
configuration.setSrcPath(properties.getProperty("srcPath"));
configuration.setPojoPackage(properties.getProperty("pojoPackage"));
} catch (IOException e) {
e.printStackTrace();
}
}


/**
* 提供connection
* @return
*/
public static Connection getConnection(){
try {
Class.forName(configuration.getDriver());
Connection connection = DriverManager.getConnection(configuration.getUrl(),
configuration.getUsername(), configuration.getPassword());
return connection;
} catch (Exception e) {
e.printStackTrace();
}

return null;
}


/**
* 返回db.properties对应的bean
* @return
*/
public static Configuration getConfiguration() {
return configuration;
}
}

2.3 根据数据库的表结构生成对应的pojo

这部分知识需要用到数据库的元数据,不理解的请参考:JDBC元数据操作(一)– DatabaseMetaData接口详解

这部分我们需要在tableContext类中完成,如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
package cn.gxm.sorm.core;

import cn.gxm.sorm.bean.ColumnInfo;
import cn.gxm.sorm.bean.Configuration;
import cn.gxm.sorm.bean.TableInfo;

import java.sql.*;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
* @author GXM www.guokangjie.cn
* @date 2019/5/15
*
* 管理数据库中的所有表和java中的对象的关系
* 可以根据表生成java对象
*/
public class TableContext {

/**
* 对应数据库中所有的表
* key 为表的名称, value为表对应的bean
*/
private static Map<String, TableInfo> allTables = new HashMap<>();


private TableContext(){
}

/**
* 获取数据库中的所有表以及相关字段,并生成对应的pojo
*/
static {
Connection connection = DBManager.getConnection();
ResultSet rs = null;
try {
// 根据metaData就可以获取到数据库的源信息(表,字段等等)
// 具体请查看 https://blog.csdn.net/chen_zw/article/details/18816599
DatabaseMetaData metaData = connection.getMetaData();

//处理表
rs = metaData.getTables(null, "%", "%", new String[]{ "TABLE" });
while (rs.next()) {
TableInfo tableInfo = new TableInfo();
String tableName = rs.getString("TABLE_NAME");
tableInfo.setName(tableName);

//处理一个表中的所有列
ResultSet set = metaData.getColumns(null, "%", tableName, "%");
Map<String, ColumnInfo> columnInfoMap = new HashMap<>();
while (set.next()){
ColumnInfo columnInfo = new ColumnInfo(set.getString("COLUMN_NAME"),
set.getString("TYPE_NAME"),1);
columnInfoMap.put(columnInfo.getName(),columnInfo);
}

//处理表中的主键,跟新字段的键类型
List<ColumnInfo> columnInfoList = new ArrayList<>();
ResultSet primaryKeys = metaData.getPrimaryKeys(null, "%", tableName);
while (primaryKeys.next()){
ColumnInfo columnInfo = columnInfoMap.get(primaryKeys.getObject("COLUMN_NAME"));
columnInfo.setKeyType(2);
columnInfoMap.put((String)primaryKeys.getObject("COLUMN_NAME"),columnInfo);
columnInfoList.add(columnInfo);

if(columnInfoList.size() == 1){
tableInfo.setOnlyPriKey(columnInfo);
}else{
// 说明为联合主键
tableInfo.setUnionKey(columnInfoList);
tableInfo.setOnlyPriKey(null);
}
}

tableInfo.setColumns(columnInfoMap);
allTables.put(tableName,tableInfo);
}
} catch (SQLException e) {
e.printStackTrace();
}

}

/**
* 对外提供获取数据库表信息的方法
* @return
*/
public static Map<String, TableInfo> getAllTables() {
return allTables;
}
}

在这里对该类做一个测试,数据库的两张表结构如下
company

1
2
3
4
5
6
CREATE TABLE `company` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`name` varchar(255) NOT NULL,
`address` varchar(255) DEFAULT NULL,
PRIMARY KEY (`id`,`name`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

emp

1
2
3
4
5
6
CREATE TABLE `emp` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`name` varchar(255) DEFAULT NULL,
`age` int(11) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

测试代码:

1
2
3
public static void main(String[] args) {
System.out.println(TableContext.getAllTables());
}

结果:(这样可能不舒服,推荐大家debug一下,在debug中看的更明显)

1
{emp=TableInfo{name='emp', columns={name=ColumnInfo{name='name', dataType='VARCHAR', keyType=1}, id=ColumnInfo{name='id', dataType='INT', keyType=2}, age=ColumnInfo{name='age', dataType='INT', keyType=1}}, onlyPriKey=ColumnInfo{name='id', dataType='INT', keyType=2}, unionKey=null}, company=TableInfo{name='company', columns={address=ColumnInfo{name='address', dataType='VARCHAR', keyType=1}, name=ColumnInfo{name='name', dataType='VARCHAR', keyType=2}, id=ColumnInfo{name='id', dataType='INT', keyType=2}}, onlyPriKey=null, unionKey=[ColumnInfo{name='id', dataType='INT', keyType=2}, ColumnInfo{name='name', dataType='VARCHAR', keyType=2}]}}

2.4 完成Mysql字段类型与java数据类型的转换

因为这里主要使用mysql所以就写这一部分即可,而且这里并不做所有数据的转化!

2.4.1 封装mysql字段类型

这样,方便以后的使用,与扩展
类MysqlType:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
package cn.gxm.sorm.core;

/**
* @author GXM www.guokangjie.cn
* @date 2019/5/16
*/
public class MySqlType {


/**
* mysql的varchar类型
*/
public static final String VARCHAR = "varchar";
/**
* mysql的bigint类型
*/
public static final String BIGINT = "bigint";
/**
* mysql的int类型
*/
public static final String INT = "int";
/**
* mysql的integer类型
*/
public static final String INTEGER = "integer";
/**
* mysql的date类型
*/
public static final String DATE = "date";
/**
* mysql的time类型
*/
public static final String TIME = "time";
/**
* mysql的double类型
*/
public static final String DOUBLE = "double";
/**
* mysql的float类型
*/
public static final String FLOAT = "float";
}

2.4.2 实现具体的mysql字段类型与java类型转化

写一个类MySqlTypeConvertor继承TypeConvertor

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
package cn.gxm.sorm.core;

import java.sql.Date;

/**
* @author GXM www.guokangjie.cn
* @date 2019/5/16
*
* 实现mysql数据库与java数据类型的转换
*/
public class MySqlTypeConvertor implements TypeConvertor{

/**
* 暂时不实现
* @param javaType java数据类型
* @return
*/
@Override
public String javaType2DBType(String javaType) {

return null;
}

/**
* 现mysql数据库到java数据类型的转换
* @param columnType 数据库类型
* @return 与之对应的Java类型
*/
@Override
public String DBType2javaType(String columnType) {
if(MySqlType.VARCHAR.equalsIgnoreCase(columnType)){
return "String";
}else if(MySqlType.INT.equalsIgnoreCase(columnType)
|| MySqlType.BIGINT.equalsIgnoreCase(columnType)
|| MySqlType.INTEGER.equalsIgnoreCase(columnType)){
return "Integer";
}else if(MySqlType.DATE.equalsIgnoreCase(columnType)
|| MySqlType.TIME.equalsIgnoreCase(columnType)){
// 注意这里转为java.sql.Date而不是java.util.date
// 如果直接转换为utils.date,则需要具体的实现,用到时,我们再转化即可
return "java.sql.Date";
}else if(MySqlType.DOUBLE.equalsIgnoreCase(columnType)){
return "Double";
}else if(MySqlType.FLOAT.equalsIgnoreCase(columnType)){
return "Float";
}

// 这里不全部转化
return "";
}

}

三、完成javaFileUtils

上一部分我们已经做到了,可以根据数据库的表获取其中的元数据了,接下就是根据元数据完成pojo,说白了,就是拼接字符串,并生成文件

3.1 为完成javaFileUtils做出必要的准备

3.1.1 为更好的解耦,我们抽取公用的方法放到工具类中

StringUtils:

1
2
3
4
5
6
7
8
/**
* 将字符串的首字母大写
* @param src
* @return
*/
public static String toUpperCaseHeadChar(String src){
return src.toUpperCase().substring(0,1)+src.substring(1);
}

3.1.2 设计一个属性的字段,与set,get方法

JavaFieldSetGet (set,get方法省略)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
package cn.gxm.sorm.bean;

/**
* @author GXM www.guokangjie.cn
* @date 2019/5/17
*
* 每一个pojo都有的java属性,set,get等等
*/
public class JavaFieldSetGet {

/**
* 例如 (private String id)
*/
private String fieldInfo;

/**
* 例如
* public String getId(){
* return id;
* }
*/
private String fieldGetInfo;

/**
* 例如
* public void setId(String id){
* this.id = id;
* }
*/
private String fieldSetInfo;
@Override
public String toString() {
return fieldInfo+fieldGetInfo+fieldSetInfo;
}
}

3.2 java的file生成我们在javaFileUtils工具类里面完成,其主要方法如下

  • generateJavaFieldSetGet 每一个pojo都有属性和set,get方法,我们生成说白了就是字符串
    该方法 测试结果如图:generateJavaFieldSetGet 方法测试结果说明

  • generateJavaSrc 根据上面的方法我们可以完成一个属性的字符串所有功能,所以我们这里再将表的数据也拼接成字符串,合并最总变为一个java的pojo(只不过是字符串)
    该方法 测试结果如图:
    generateJavaSrc 方法测试结果说明

  • generatePOJOFile 对外提供方法生成pojo文件

其代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
/**
* @author GXM www.guokangjie.cn
* @date 2019/5/15
*
* 操作文件(暂时先将columnInfo转为对应的java代码)
*/
public class JavaFileUtils {


/**
* 根据传入的数据库字段信息生成java的bean代码
* varchar id -> private String id 以及相应set与get方法
* @param columnInfo 数据库字段信息
* @param typeConvertor 对应的数据库字段数据类型转换
* @return 一个属性的bean代码
*/
private static JavaFieldSetGet generateJavaFieldSetGet(@NotNull ColumnInfo columnInfo,
@NotNull TypeConvertor typeConvertor){
if(columnInfo==null || typeConvertor == null){
throw new SormException("ColumnInfo or TypeConvertor is null");
}

JavaFieldSetGet fieldSetGet = new JavaFieldSetGet();

// 字段名称 id
String columnName = columnInfo.getName();
String javaFieldType = typeConvertor.DBType2javaType(columnInfo.getDataType());

//private String id
String fieldInfo = "\tprivate "+ javaFieldType +" "+ columnName +";\n";

/**
* public void getId(){
* return id;
* }
*/
StringBuilder fieldGetInfo = new StringBuilder("\tpublic "+javaFieldType+" get"+StringUtils.toUpperCaseHeadChar(columnName)+" (){\n");
fieldGetInfo.append("\t\treturn "+ columnName+";\n");
fieldGetInfo.append("\t}\n");

/**
* public void setId(String id){
* this.id = id;
* }
*/
StringBuilder fieldSetInfo = new StringBuilder("\tpublic void set"+StringUtils.toUpperCaseHeadChar(columnName)+" ("+javaFieldType+" "+columnName+"){\n");
fieldSetInfo.append("\t\tthis."+columnName+"="+ columnName+";\n");
fieldSetInfo.append("\t}\n");

fieldSetGet.setFieldInfo(fieldInfo);
fieldSetGet.setFieldGetInfo(fieldGetInfo.toString());
fieldSetGet.setFieldSetInfo(fieldSetInfo.toString());

return fieldSetGet;
}


/**
* 根据表结构生成对应的pojo
* @param tableInfo 表信息
* @param typeConvertor java与数据库字段类型转化器
* @param author 生成的Pojo的作者
* @return pojo字符串
*/
private static String generateJavaSrc(@NotNull TableInfo tableInfo,
@NotNull TypeConvertor typeConvertor,String author){

if(tableInfo==null || typeConvertor == null){
throw new SormException("tableInfo or TypeConvertor is null");
}

StringBuilder javaResourse = new StringBuilder();
// 生成package 例如 package cn.gxm.sorm.pojo
javaResourse.append("package "+ DBManager.getConfiguration().getPojoPackage()+";\n\n");

// 生成import 例如 import java.sql.Date
javaResourse.append("import java.sql.*;\n");
javaResourse.append("import java.lang.*;\n");

// 生成autor 以及日期等java文件说明
/**
* @author GXM www.guokangjie.cn
* @date 2019/5/17
*/
javaResourse.append("/**\n")
.append(" * @author "+author+"\n")
.append(" * @date "+new SimpleDateFormat("yyyy/MM/dd").format(new Date())+"\n")
.append(" */\n");
// 生成类的开始 例如 public class Emp {
javaResourse.append("public class "+StringUtils.toUpperCaseHeadChar(tableInfo.getName())+"{\n");

// 生成类的属性
Set<Map.Entry<String, ColumnInfo>> entries = tableInfo.getColumns().entrySet();
for (Map.Entry<String, ColumnInfo> entry: entries){
JavaFieldSetGet fieldSetGet = generateJavaFieldSetGet(entry.getValue(), typeConvertor);
javaResourse.append(fieldSetGet+"\n");
}

// 生成类的结束 }
javaResourse.append("}");
return javaResourse.toString();

}


/**
* 生成与数据库字段匹配的java的pojo
* @param tableInfo 表信息
* @param typeConvertor java与数据库字段类型转化器
* @param author 生成的Pojo的作者
*/
public static void generatePOJOFile(TableInfo tableInfo,TypeConvertor typeConvertor,String author){
String javaSrc = generateJavaSrc(tableInfo, typeConvertor, author);
String dirPath = DBManager.getConfiguration().getSrcPath()+"//"+DBManager.getConfiguration().getPojoPackage().replaceAll("\\.","//");
File pojoDir = new File(dirPath);
//包不存在则创建包
if(!pojoDir.exists()){
pojoDir.mkdirs();
}

Writer writer = null;
try {
writer = new FileWriter(pojoDir.getAbsoluteFile()+"//"+StringUtils.toUpperCaseHeadChar(tableInfo.getName())+".java");
writer.write(javaSrc);
writer.flush();
} catch (IOException e) {
e.printStackTrace();
}finally {
try {
writer.close();
System.out.println("pojo创建成功!!");
} catch (IOException e) {
e.printStackTrace();
}
}

}

四、完善TableContext

我们已经在TableContext类中获取了所有的数据库源信息(第一部分与第二部分),而在第三部分我们也完成了根据tableInfo来生成pojo的(.java文件),但是JavaFileUtils总归是一个工具类,我们将在TableContext对外提供生成的方法,即在TableContext类中添加方法

1
2
3
4
5
6
7
8
9
/**
* 生成或者跟新java的pojo与数据库的对应关系
*/
public static void generateOrUpdateJavaFilePojo(){
Set<Map.Entry<String, TableInfo>> entries = allTables.entrySet();
for (Map.Entry<String, TableInfo> entry: entries){
JavaFileUtils.generatePOJOFile(entry.getValue(),new MySqlTypeConvertor(),"GXM www.guokangjie.cn");
}
}

测试
配置正确的db.properties,就会在指定位置生成与数据库对应的pojo,如下图
pojo文件生成测试

希望对您有所帮助
May all the ordinary are great, all the ignoble bloom
  • smile