King's Studio

手写简单Object Relationship Mapping框架

字数统计: 6.2k阅读时长: 29 min
2019/02/28 Share

前言

为了将最近学习的JDBC、反射、设计模式综合起来,就学习手写了ORM框架,ORM全称Object Relationship Mapping,意思是对象关系映射,主要思想有三点:1、数据库中的表结构和类结构对应;2、表中字段和类属性对应;3、表中记录和类的对象对应。这就是实现对象关系映射的基本要求,对应到框架中就会有很多接口和实现类。

具体实现

核心架构

框架src目录下一共有四个package,分别是:bean包,负责封装数据库表中的信息以及生成与表结构对应的类;core包,负责与数据库的连接操作以及数据类型的转换;
pool包,连接池功能,加入连接池,使查询的效率大幅提高;utils包,这个包下的类都是常用的工具类,包括反射、JDBC查询操作、字符串操作等。src目录下还有一个db.properties文件,这个文件中保存着包括驱动类、数据库连接url、数据库用户名密码等信息,后面创建工程使用本框架时,直接在该文件中修改相关信息即可。下面我们讲一讲框架内部具体的实现。

bean包

ColumnInfo类

ColumnInfo类的主要作用是封装了表中一个字段的信息,包括字段名称、字段的数据类型、字段的键类型。后面我们要获取表中一个字段的名称、类型、键类型都通过这个类的对象的ge方法获得。

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
/**
* 封装了表中一个字段的信息
* @author jinqi
*
*/
public class ColumnInfo {

/**
* 字段名称
*/
private String name;

/**
* 字段的数据类型
*/
private String dataType;

/**
* 字段的键类型(0:普通键,1:主键,2:外键)
*/
private int keyType;

public String getName() {
return name;
}

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

public String getDataType() {
return dataType;
}

public void setDataType(String dataType) {
this.dataType = dataType;
}

public int getKeyType() {
return keyType;
}

public void setKeyType(int keyType) {
this.keyType = keyType;
}

public ColumnInfo(String name, String dataType, int keyType) {
super();
this.name = name;
this.dataType = dataType;
this.keyType = keyType;
}

public ColumnInfo() {

}
}

Configuration类

Configuration类用于管理配置信息,它将db.properties文件中的信息全部封转成私有属性,为后续的数据库连接做准备。

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
/**
* 管理配置信息
* @author jinqi
*
*/
public class Configuration {
/**
* 驱动类
*/
private String driver ;
/**
* jdbc的url
*/
private String url ;
/**
* 数据库的用户名
*/
private String user ;
/**
* 数据库的密码
*/
private String pwd ;
/**
* 正在使用哪个数据库
*/
private String usingDB ;
/**
* 项目的源码路径
*/
private String srcPath ;
/**
* 扫描生成java类的包(po:Persistence Object持久化对象)
*/
private String poPackage ;

/**
* 项目使用的查询类是哪一个类
*/
private String queryClass;

/**
* 连接池中的最小连接数
*/
private int poolMinSize;

/**
* 连接池中的最大连接数
*/
private int poolMaxSize;


public int getPoolMinSize() {
return poolMinSize;
}

public void setPoolMinSize(int poolMinSize) {
this.poolMinSize = poolMinSize;
}

public int getPoolMaxSize() {
return poolMaxSize;
}

public void setPoolMaxSize(int poolMaxSize) {
this.poolMaxSize = poolMaxSize;
}

public Configuration() {
}

public Configuration(String driver, String url, String user, String pwd, String usingDB, String srcPath,
String poPackage) {
super();
this.driver = driver;
this.url = url;
this.user = user;
this.pwd = pwd;
this.usingDB = usingDB;
this.srcPath = srcPath;
this.poPackage = poPackage;
}

public String getQueryClass() {
return queryClass;
}

public void setQueryClass(String queryClass) {
this.queryClass = queryClass;
}

public String getDriver() {
return driver;
}
public void setDriver(String driver) {
this.driver = driver;
}
public String getUrl() {
return url;
}
public void setUrl(String url) {
this.url = url;
}
public String getUser() {
return user;
}
public void setUser(String user) {
this.user = user;
}
public String getPwd() {
return pwd;
}
public void setPwd(String pwd) {
this.pwd = pwd;
}
public String getUsingDB() {
return usingDB;
}
public void setUsingDB(String usingDB) {
this.usingDB = usingDB;
}
public String getSrcPath() {
return srcPath;
}
public void setSrcPath(String srcPath) {
this.srcPath = srcPath;
}
public String getPoPackage() {
return poPackage;
}
public void setPoPackage(String poPackage) {
this.poPackage = poPackage;
}
}

JavaFieldGetSet类

JavaFieldGetSet类封装了java属性和get、set方法的源代码,如属性的源码信息、get方法的源码信息、set方法的源码信息,都以字符串类型保存,为后面生成与数据库表对应的Java类做准备。

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
/**
*
* 封装了java属性和get、set方法的源代码
* @author jinqi
*
*/
public class JavaFieldGetSet {

/**
* 属性的源码信息,如:private int userId;
*/
private String fieldInfo;
/**
* get方法的源码信息,如:public int getUserId(){}
*/
private String getInfo;
/**
* set方法的源码信息,如:public void setUserId(int id){this.id=id;}
*/
private String setInfo;

@Override
public String toString() {
System.out.println(fieldInfo);
System.out.println(getInfo);
System.out.println(setInfo);
return super.toString();
}

public String getFieldInfo() {
return fieldInfo;
}
public void setFieldInfo(String fieldInfo) {
this.fieldInfo = fieldInfo;
}
public String getGetInfo() {
return getInfo;
}
public void setGetInfo(String getInfo) {
this.getInfo = getInfo;
}
public String getSetInfo() {
return setInfo;
}
public void setSetInfo(String setInfo) {
this.setInfo = setInfo;
}
public JavaFieldGetSet(String fieldInfo, String getInfo, String setInfo) {
super();
this.fieldInfo = fieldInfo;
this.getInfo = getInfo;
this.setInfo = setInfo;
}

public JavaFieldGetSet() {
}
}

TableInfo类

TableInfo类用来存储表结构的信息,包括表名、所有字段的信息、唯一的主键等,通过TableInfo对象能够获得该表的主键等信息,为后续执行SQL语句对数据库进行操作服务。

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
/**
* 存储表结构的信息
* @author jinqi
*
*/
public class TableInfo {
/**
* 表名
*/
private String tname;

/**
* 所有字段的信息
*/
private Map<String, ColumnInfo> columns;

/**
* 唯一主键(只能处理表中有且只有一个主键的情况)
*/
private ColumnInfo onlyPriKey;

/**
* 如果联合主键,则在这里存储
*/
private List<ColumnInfo> priKeys;

public List<ColumnInfo> getPriKeys() {
return priKeys;
}

public void setPriKeys(List<ColumnInfo> priKeys) {
this.priKeys = priKeys;
}

public String getTname() {
return tname;
}

public void setTname(String tname) {
this.tname = tname;
}

public Map<String, ColumnInfo> getColumns() {
return columns;
}

public void setColumns(Map<String, ColumnInfo> columns) {
this.columns = columns;
}

public ColumnInfo getOnlyPriKey() {
return onlyPriKey;
}

public void setOnlyPriKey(ColumnInfo onlyPriKey) {
this.onlyPriKey = onlyPriKey;
}

public TableInfo(String tname, Map<String, ColumnInfo> columns, ColumnInfo onlyPriKey) {
super();
this.tname = tname;
this.columns = columns;
this.onlyPriKey = onlyPriKey;
}

public TableInfo() {
}

public TableInfo(String tname, List<ColumnInfo> priKeys, Map<String, ColumnInfo> columns) {
super();
this.tname = tname;
this.columns = columns;
this.priKeys = priKeys;
}
}

core包

CallBack接口

CallBack接口是回调接口, 提供doExecut方法的定义,方便后面Query类不同的查询需求重新实现doExecute方法。

1
2
3
4
5
6
7
8
/**
* 回调接口
* @author jinqi
*
*/
public interface CallBack {
public Object doExecute(Connection conn,PreparedStatement ps,ResultSet rs);
}

DBManager类

DBManager类根据配置信息,维持连接对象的管理。通过静态代码块,在加载该类的同时加载且只加载一次配置信息,并且提供获得和创建Connetcion对象的两个方法,以及关闭ResultSet、Statement、Connection的close方法。在创建了连接池对象pool后,大大提高了效率。

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
/**
* 根据配置信息,维持连接对象的管理(增加连接池)
* @author jinqi
*
*/
public class DBManager {
private static Configuration conf;

//创建连接池对象,就是从DBConnPool中取出一个池
private static DBConnPool pool;

static {//静态代码块,只需要加载一次配置信息
Properties pros = new Properties();
try {
pros.load(Thread.currentThread().getContextClassLoader().getResourceAsStream("db.properties"));
} catch (IOException e) {
e.printStackTrace();
}

conf = new Configuration();
conf.setDriver(pros.getProperty("driver"));
conf.setPoPackage(pros.getProperty("poPackage"));
conf.setPwd(pros.getProperty("pwd"));
conf.setSrcPath(pros.getProperty("srcPath"));
conf.setUrl(pros.getProperty("url"));
conf.setUser(pros.getProperty("user"));
conf.setUsingDB(pros.getProperty("usingDB"));
conf.setQueryClass(pros.getProperty("queryClass"));
conf.setPoolMaxSize(Integer.parseInt(pros.getProperty("poolMaxSize")));
conf.setPoolMinSize(Integer.parseInt(pros.getProperty("poolMinSize")));

//加载TableContext类
System.out.println(TableContext.class);
}

/**
* 创建新的Connection对象
* @return
*/
public static Connection createConn() {
try {
Class.forName(conf.getDriver());
return DriverManager.getConnection(conf.getUrl(),
conf.getUser(),conf.getPwd());//直接建立连接,后期增加连接池处理,提高效率。
} catch (Exception e) {
e.printStackTrace();
return null;
}
}

/**
* 获得Connection对象
* @return
*/
public static Connection getConn() {
if (pool==null) {
pool = new DBConnPool();
}
return pool.getConnection();
}

/**
* 关闭传入的ResultSet、Statement、Connection对象
* @param rs
* @param ps
* @param conn
*/
public static void close(ResultSet rs,Statement ps,Connection conn) {
if(rs!=null) {
try {
rs.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if(ps!=null) {
try {
ps.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
pool.close(conn);
}

/**
* 关闭传入的Statement、Connection对象
* @param rs
* @param ps
* @param conn
*/
public static void close(Statement ps,Connection conn) {
if(ps!=null) {
try {
ps.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
pool.close(conn);
}

public static void close(Connection conn) {
pool.close(conn);
}

/**
* 返回Configuration对象
* @return
*/
public static Configuration getConf() {
return conf;
}
}

MySqlQuery类

MySqlQuery类负责针对Mysql数据库的查询,继承于Query类,由于后来涉及到数据库可能以后会更换,就将查询操作全部归入到了抽象类Query类中去,因为查询的操作大同小异,对这个类不再做赘述。

1
2
3
4
5
6
7
8
9
10
11
12
/**
* 负责针对Mysql数据库的查询
* @author jinqi
*
*/
@SuppressWarnings("all")
public class MySqlQuery extends Query{
@Override
public Object queryPagenate(int pageNum, int size) {
return null;
}
}

MySqlTypeConvertor类

MySqlTypeConvertor类实现了TypeConvertor接口,该类主要用于将MySql数据库中的数据类型转为Java数据类型,因为我们针对的是对数据库的操作,这里只提供从数据库到Java的类型转换。

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
/**
* mysql数据类型和java数据类型的转换
* @author jinqi
*
*/
public class MySqlTypeConvertor implements TypeConvertor{
/**
* mysql数据类型和java数据类型的转换
*/
@Override
public String databaseType2JavaType(String columnType) {

//varchar-->String
if ("varchar".equalsIgnoreCase(columnType)||"char".equalsIgnoreCase(columnType)) {
return "String";
}else if ("int".equalsIgnoreCase(columnType)
||"tinyint".equalsIgnoreCase(columnType)
||"smallint".equalsIgnoreCase(columnType)
||"integer".equalsIgnoreCase(columnType)
) {
return "Integer";
}else if ("bigint".equalsIgnoreCase(columnType)) {
return "Long";
}else if ("double".equalsIgnoreCase(columnType)||"float".equalsIgnoreCase(columnType)) {
return "Double";
}else if ("clob".equalsIgnoreCase(columnType)) {
return "java.sql.Clob";
}else if ("blob".equalsIgnoreCase(columnType)) {
return "java.sql.Blob";
}else if ("date".equalsIgnoreCase(columnType)) {
return "java.sql.Date";
}else if ("time".equalsIgnoreCase(columnType)) {
return "java.sql.Time";
}else if ("timestamp".equalsIgnoreCase(columnType)) {
return "java.sql.Timestamp";
}
return null;
}

@Override
public String javaType2DatabaseType(String javaDataType) {
return null;
}
}

Query抽象类

Query抽象类是负责查询的,对外提供服务的核心类,提供executeQueryTemplate方法,是JDBC操作的模板方法;提供executeDML执行DML语句方法;提供insert、delete、update的增加、删除、更改方法,还提供了五种查询方法,具体实现见代码。

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
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
/**
* 负责查询(对外提供服务的核心类)
* @author jinqi
*
*/
@SuppressWarnings("all")
public abstract class Query implements Cloneable{

/**
* 采用模板方法模式将JDBC操作封装成模板,便于重用,就是将数据库连接操作封装起来,在下一个类需要使用时,直接调用该模板
* @param sql sql语句
* @param params sql的参数
* @param clazz 记录要封装的java类
* @param back CallBack的实现类,实现回调
* @return
*/
public Object executeQueryTemplate(String sql,Object[] params,Class clazz,CallBack back) {
Connection conn = DBManager.getConn();
PreparedStatement ps = null;
ResultSet rs = null;
try {
ps=conn.prepareStatement(sql);

//给sql设参数
JDBCUtils.handleParams(ps, params);
System.out.println(ps);
rs = ps.executeQuery();

return back.doExecute(conn, ps, rs);

} catch (Exception e) {
e.printStackTrace();
return null;
}finally {
DBManager.close(ps, conn);
}
}

/**
* 直接执行一个DML语句
* @param sql sql语句
* @param params 参数
* @return 执行sql语句执行后影像记录的行数
*/
public int executeDML(String sql,Object[] params) {

Connection conn = DBManager.getConn();
int count = 0;
PreparedStatement ps = null;
try {
ps=conn.prepareStatement(sql);

//给sql设参数
JDBCUtils.handleParams(ps, params);
System.out.println(ps);
count = ps.executeUpdate();
} catch (Exception e) {
e.printStackTrace();
}finally {
DBManager.close(ps, conn);
}
return count;
}

/**
* 将一个对象存储到数据库中
* 把对象中不为null的属性存进数据库中,如果数字为null则放0
* @param obj 要存储的对象
*/
public void insert(Object obj) {
//obj-->表 insert into 表名 (id,uname,pwd) values (?,?,?)
Class c = obj.getClass();
List<Object> params = new ArrayList<Object>();//存储sql的参数对象
TableInfo tableInfo = TableContext.poClassTableMap.get(c);
StringBuilder sql = new StringBuilder("insert into "+tableInfo.getTname()+" (");
int countNotNullField = 0;//计算属性不为空的数量
Field[] fs = c.getDeclaredFields();//取出所有不为空的属性
for(Field f:fs) {
String fieldName = f.getName();
Object fieldValue = ReflectUtils.invokeGet(fieldName, obj);
if (fieldValue!=null) {
countNotNullField++;
sql.append(fieldName+",");
params.add(fieldValue);
}
}
//在循环结束时,将最后的逗号换成右括号
sql.setCharAt(sql.length()-1, ')');
sql.append(" values (");
//添加用问号表示的参数
for(int i=0;i<countNotNullField;i++) {
sql.append("?,");
}
//将循环结束时最后的逗号换成右括号
sql.setCharAt(sql.length()-1, ')');
//将sql转成String类型,params转为Array类型去执行DML语句
executeDML(sql.toString(), params.toArray());
}

/**
* 删除clazz表示类对应的表中的记录(指定主键值id的记录)
* @param clazz 跟表对应的类的Class对象
* @param id 主键的值
*/
public void delete(Class clazz,Object id) {

//Emp.class,2-->delete from emp where id=2
//通过Class对象找TableInfo
TableInfo tableInfo = TableContext.poClassTableMap.get(clazz);
//获得主键
ColumnInfo onlyPriKey = tableInfo.getOnlyPriKey();

String sql = "delete from "+tableInfo.getTname()+" where "+onlyPriKey.getName()+"=? ";

executeDML(sql, new Object[] {id});
}

/**
* 删除对象在数据库中对应的记录(对象所在的类对应到表,对象的主键的值对应到记录)
* @param obj
*/
public void delete(Object obj) {

Class c = obj.getClass();
TableInfo tableInfo = TableContext.poClassTableMap.get(c);
ColumnInfo onlyPrikey = tableInfo.getOnlyPriKey();//获得主键

//通过反射机制,调用属性对应的get/set方法
Object priKeyValue = ReflectUtils.invokeGet(onlyPrikey.getName(), obj);
delete(c,priKeyValue);
}

/**
* 更新对象对应的记录,并且只更新指定的字段的值
* @param obj 要更新的对象
* @param fieldNames 更新的属性列表
* @return 执行sql语句影响记录的行数
*/
public int update(Object obj,String[] fieldNames) {

//obj{"uname","pwd"}-->update 表名 set uname=?,pwd=? where id=?
Class c = obj.getClass();
List<Object> params = new ArrayList<Object>();//存储sql的参数对象
TableInfo tableInfo = TableContext.poClassTableMap.get(c);
ColumnInfo priKey = tableInfo.getOnlyPriKey();//获得唯一主键
StringBuilder sql = new StringBuilder("update "+tableInfo.getTname()+" set ");

for(String fname:fieldNames) {
Object fvalue = ReflectUtils.invokeGet(fname, obj);
params.add(fvalue);
sql.append(fname+"=?,");
}
//将循环最后产生的多的一个逗号换成空格
sql.setCharAt(sql.length()-1, ' ');
sql.append(" where ");
sql.append(priKey.getName()+"=?");
params.add(ReflectUtils.invokeGet(priKey.getName(), obj));//问号代表的主键的值
return executeDML(sql.toString(), params.toArray());
}

/**
* 查询返回多行记录,并将每行记录封装到clazz指定的类的对象中
* @param sql 查询语句
* @param clazz 封装数据的javabean类的Class对象
* @param params sql的参数
* @return 查询到的结果
*/
public List queryRows(final String sql,final Class clazz,final Object[] params) {

//调用执行模板,不需要再重复写JDBC连接操作
return (List)executeQueryTemplate(sql, params, clazz, new CallBack() {

@Override
public Object doExecute(Connection conn, PreparedStatement ps, ResultSet rs) {
List list = null;
try {
ResultSetMetaData metaData = rs.getMetaData();
//多行
while(rs.next()) {
if (list==null) {
list = new ArrayList();
}

Object rowObj = clazz.newInstance();//调用javabean的无参构造器

//多列 select username,pwd,age from user where id>? and age>18
for(int i =0;i<metaData.getColumnCount();i++) {
String columnName = metaData.getColumnLabel(i+1);//username
Object columnValue = rs.getObject(i+1);

//调用rowObj对象的serUsername(String uname)方法,将column的值设置进去
ReflectUtils.invokeSet(rowObj, columnName, columnValue);
}
list.add(rowObj);
}
} catch (Exception e) {
e.printStackTrace();
}
return list;
}
});
}

/**
* 查询返回一行记录,并将该记录封装到clazz指定的类的对象中
* @param sql 查询语句
* @param clazz 封装数据的javabean类的Class对象
* @param params sql的参数
* @return 查询到的结果
*/
public Object queryUniqueRow(String sql,Class clazz,Object[] params) {
List list = queryRows(sql, clazz, params);
return (list!=null&&list.size()>0)?list.get(0):null;
}

/**
* 根据主键的值直接查找对应的对象
* @param clazz
* @param id
* @return
*/
public Object queryById(Class clazz,Object id) {
//select * from emp where id=?
TableInfo tableInfo = TableContext.poClassTableMap.get(clazz);
//获得主键
ColumnInfo onlyPriKey = tableInfo.getOnlyPriKey();
String sql="select * from "+tableInfo.getTname()+" where "+onlyPriKey.getName()+"=?";
return queryUniqueRow(sql, clazz, new Object[] {id});
}

/**
* 查询返回一个值(一行一列),并将该值返回
* @param sql 查询语句
* @param params sql的参数
* @return 查询到的结果
*/
public Object queryValue(String sql,Object[] params) {

return executeQueryTemplate(sql, params, null, new CallBack() {

@Override
public Object doExecute(Connection conn, PreparedStatement ps, ResultSet rs) {
Object value = null;//存储查询结果的对象
try {
while(rs.next()) {
value = rs.getObject(1);
}
} catch (SQLException e) {
e.printStackTrace();
}
return value;
}
});
}

/**
* 查询返回一个数字(一行一列),并将该值返回
* @param sql 查询语句
* @param params sql的参数
* @return 查询到的数字
*/
public Number queryNumber(String sql,Object[] params) {
return (Number)queryValue(sql, params);
}

/**
* 分页查询
* @param pageNum 第几页数据
* @param size 每页显示多少条记录
* @return
*/
public abstract Object queryPagenate(int pageNum,int size);

@Override
protected Object clone() throws CloneNotSupportedException {
return super.clone();
}
}

QueryFactory类

QueryFactory类是创建Query对象的工厂类,用到了克隆模式,单例模式,工厂模式,我对设计模式这块掌握的不是很清楚,后面会着重去研究一下。

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
/**
*
* 创建Query对象的工厂类
* 克隆模式,单例模式,工厂模式
* @author jinqi
*
*/

public class QueryFactory {

private static Query prototypeObj;//原型对象

static {
try {
Class c = Class.forName(DBManager.getConf().getQueryClass());
prototypeObj = (Query)c.newInstance();
} catch (Exception e) {
e.printStackTrace();
}
}

private QueryFactory() {//私有构造器

}

public static Query createQuery() {
try {
return (Query)prototypeObj.clone();
} catch (CloneNotSupportedException e) {
e.printStackTrace();
return null;
}
}
}

TableContext类

TableContext类负责获取管理数据库所有表结构和类结构的关系,并可以表结构生成类结构。静态代码块用于获取数据库对应的表的信息,updateJavaPOFile方法根据表结构生成对应的类结构,loadPOTables方法用于加载po包下面的类。

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
/**
* 负责获取管理数据库所有表结构和类结构的关系,并可以表结构生成类结构
* @author jinqi
*
*/
public class TableContext {

/**
* 表名为Key,表信息对象为value
*/
public static Map<String, TableInfo> tables = new HashMap<String, TableInfo>();

/**
* 将po的class对象和表信息对象关联起来,便于重用。
*/
public static Map<Class, TableInfo> poClassTableMap = new HashMap<Class,TableInfo>();

public TableContext() {
}

static {
try {
//初始化获得表的信息
Connection con = DBManager.getConn();
DatabaseMetaData dbmd = con.getMetaData();
ResultSet tableRet = dbmd.getTables(null, "%", "%", new String[]{"TABLE"});
while(tableRet.next()) {
String tableName = (String)tableRet.getObject("TABLE_NAME");
TableInfo ti = new TableInfo(tableName,new ArrayList<ColumnInfo>(),new HashMap<String,ColumnInfo>());
tables.put(tableName, ti);
ResultSet set = dbmd.getColumns(null, "%", tableName, "%");//查询表中的所有字段
while(set.next()) {
ColumnInfo ci = new ColumnInfo(set.getString("COLUMN_NAME"),set.getString("TYPE_NAME"),0);
ti.getColumns().put(set.getString("COLUMN_NAME"), ci);
}
ResultSet set2 = dbmd.getPrimaryKeys(null, "%",tableName);//查询表中的主键
while(set2.next()) {
ColumnInfo ci2 = (ColumnInfo)ti.getColumns().get(set2.getObject("COLUMN_NAME"));
ci2.setKeyType(1);//设置为主键类型
ti.getPriKeys().add(ci2);
}
if (ti.getPriKeys().size()>0) {//取唯一主键,方便使用
ti.setOnlyPriKey(ti.getPriKeys().get(0));
}
}
} catch (SQLException e) {
e.printStackTrace();
}
//类加载时就更新创建的类的结构
updateJavaPOFile();

//加载po包下面的类,便于重用,提高效率。
loadPOTables();
}

/**
* 根据表结构,更新配置的po包下面的java类
* 实现了从表结构转化到类
*/
public static void updateJavaPOFile() {
Map<String,TableInfo> map = TableContext.tables;
for(TableInfo t:map.values()) {
JavaFileUtils.createJavaPOFile(t, new MySqlTypeConvertor());
}
}

/**
* 加载po包下面的类
*/
public static void loadPOTables() {
for(TableInfo tableInfo:tables.values()) {
try {
Class c = Class.forName(DBManager.getConf().getPoPackage()+"."+StringUtils.firstChar2UpperCase(tableInfo.getTname()));
poClassTableMap.put(c, tableInfo);
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
}

public static void main(String[] args) {
Map<String, TableInfo> tables = TableContext.tables;
System.out.println(tables);
}
}

TypeConvertor接口

TypeConvertor接口负责java数据类型和数据库数据类型的转换,对外提供将数据库数据类型转换成Java的数据类型的databaseType2JavaType方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
/**
* 负责java数据类型和数据库数据类型的转换
* @author jinqi
*
*/
public interface TypeConvertor {

/**
* 将数据库数据类型转换成java的数据类型
* @param columnType 数据库字段的数据类型
* @return java的数据类型
*/
public String databaseType2JavaType(String columnType);

/**
* 将java数据类型转换成数据库数据类型
* @param javaDataType java数据类型
* @return 数据库数据类型
*/
public String javaType2DatabaseType(String javaDataType);
}

pool包

DBConnPool类

DBConnPool类是连接池类,连接池的主要逻辑是我们创建一个连接池对象,并且设置连接池中初始连接的个数,当有需要连接时就从连接池中取出,用完再放回连接池中,而不是直接关闭连接,这样能大大提高处理效率。

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
/**
* 连接池的类
* @author jinqi
*
*/
public class DBConnPool {
/**
* 连接池对象
*/
private List<Connection> pool;

/**
* 最大连接数
*/
private static final int POOL_MAX_SIZE = DBManager.getConf().getPoolMaxSize();

/**
* 最小连接数
*/
private static final int POOL_MIN_SIZE = DBManager.getConf().getPoolMinSize();

/**
* 初始化连接池,使池中的连接数达到最小值
*/
public void initPool() {
if (pool==null) {
pool = new ArrayList<Connection>();
}
while(pool.size()<DBConnPool.POOL_MIN_SIZE) {
pool.add(DBManager.createConn());
System.out.println("初始化池,池中连接数:"+pool.size());
}
}

/**
* 从连接池中取出一个连接
* @return
*/
public synchronized Connection getConnection() {
int last_index = pool.size()-1;//取连接池中最后一个连接
Connection conn = pool.get(last_index);
pool.remove(last_index);//取出来后就删掉,这样其他类就无法获取到这个连接
return conn;
}

/**
* 将连接放回池中
* @param conn
*/
public synchronized void close(Connection conn) {
if (pool.size()>POOL_MAX_SIZE) {//如果池中已经满了
try {
if (conn!=null) {
conn.close();//就关闭连接
}
} catch (SQLException e) {
e.printStackTrace();
}
}else {
pool.add(conn);
}
}

public DBConnPool() {
initPool();
}
}

utils包

JavaFileUtils类

JavaFileUtils类封装了生成Java文件常用的操作,提供createFieldGetSetSRC方法根据字段信息生成Java属性信息,createJavaSrc方法根据表信息生成Java类的源代码,createJavaPOFile方法生成po包下的Java类。

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
/**
* 封装了生成Java文件常用的操作
* @author jinqi
*
*/
public class JavaFileUtils {

/**
* 根据字段信息生成java属性信息。如:varchar username--> private String username; 以及相应的set和get方法
* @param column 字段信息
* @param convertor 类型转换器
* @return java属性和set/get方法源码
*/
public static JavaFieldGetSet createFieldGetSetSRC(ColumnInfo column,TypeConvertor convertor) {
JavaFieldGetSet jfjs = new JavaFieldGetSet();
String javaFieldType = convertor.databaseType2JavaType(column.getDataType());
//生成属性
jfjs.setFieldInfo("\tprivate "+javaFieldType+" "+column.getName()+";\n");

//生成get方法
//public String getUsername(){return username;}
StringBuilder getSrc = new StringBuilder();
getSrc.append("\tpublic "+javaFieldType+" get"+StringUtils.firstChar2UpperCase(column.getName())+"(){\n");
getSrc.append("\t\treturn "+column.getName()+";\n");
getSrc.append("\t}\n");
jfjs.setGetInfo(getSrc.toString());


//生成set方法
//public void setUsername(String username){this.username = username;}
StringBuilder setSrc = new StringBuilder();
setSrc.append("\tpublic void set"+StringUtils.firstChar2UpperCase(column.getName())+"(");
setSrc.append(javaFieldType+" "+column.getName()+"){\n");
setSrc.append("\t\tthis. "+column.getName()+"="+column.getName()+";\n");
setSrc.append("\t}\n");
jfjs.setSetInfo(setSrc.toString());

return jfjs;
}

/**
* 根据表信息生成java类的源代码
* @param tableInfo 表信息
* @param convertor 数据类型转换器
* @return Java类的源代码
*/
public static String createJavaSrc(TableInfo tableInfo,TypeConvertor convertor) {
Map<String, ColumnInfo> columns= tableInfo.getColumns();
List<JavaFieldGetSet> javaFields = new ArrayList<JavaFieldGetSet>();
for(ColumnInfo c:columns.values()) {
javaFields.add(createFieldGetSetSRC(c, convertor));
}
StringBuilder src = new StringBuilder();
//生成package语句
src.append("package "+DBManager.getConf().getPoPackage()+";\n\n");
//生成import语句
src.append("import java.sql.*;\n");
src.append("import java.util.*;\n\n");
//生成类声明语句
src.append("public class "+StringUtils.firstChar2UpperCase(tableInfo.getTname())+" {\n\n");

//生成属性列表
for(JavaFieldGetSet f:javaFields) {
src.append(f.getFieldInfo());
}
src.append("\n\n");
//生成get方法列表
for(JavaFieldGetSet f:javaFields) {
src.append(f.getGetInfo());
}
//生成set方法列表
for(JavaFieldGetSet f:javaFields) {
src.append(f.getSetInfo());
}

//生成结束符
src.append("}");
return src.toString();
}

/**
* 在po包下生成对应的java类
* @param tableInfo
* @param convertor
*/
public static void createJavaPOFile(TableInfo tableInfo,TypeConvertor convertor) {
String src = createJavaSrc(tableInfo, convertor);
String srcPath = DBManager.getConf().getSrcPath()+"/";
String packagePath = DBManager.getConf().getPoPackage().replaceAll("\\.", "/");
File f = new File(srcPath+packagePath);
if (!f.exists()) {//如果指定目录不存在,则帮助用户创建
f.mkdirs();
}
BufferedWriter bw = null;
try {
bw = new BufferedWriter(new FileWriter(f.getAbsolutePath()+"/"+StringUtils.firstChar2UpperCase(tableInfo.getTname())+".java"));
bw.write(src);
System.out.println("建立表"+tableInfo.getTname()+
"对应的java类:"+StringUtils.firstChar2UpperCase(tableInfo.getTname())+".java");
} catch (IOException e) {
e.printStackTrace();
}finally {
if (bw!=null) {
try {
bw.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}

public static void main(String[] args) {
Map<String,TableInfo> map = TableContext.tables;
for(TableInfo t:map.values()) {
createJavaPOFile(t, new MySqlTypeConvertor());
}
}
}

JDBCUtils类

JDBCUtils类封装了JDBC查询常用的操作,主要是给sql设参数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
/**
* 封装了JDBC查询常用的操作
* @author jinqi
*
*/
public class JDBCUtils {

/**
* 给sql设参数
* @param ps 预编译sql语句对象
* @param params 参数
*/
public static void handleParams(PreparedStatement ps,Object[] params) {
if (params!=null) {
for(int i =0;i<params.length;i++) {
try {
ps.setObject(1+i, params[i]);
} catch (SQLException e) {
e.printStackTrace();
}
}
}
}
}

ReflectUtils类

ReflectUtils类封装了反射常用的操作,反射这一块也不是很熟悉,后续需要深入学习。

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
/**
* 封装了反射常用的操作
* @author jinqi
*
*/
public class ReflectUtils {

/**
* 调用obj对象对应属性fieldName的get方法
* @param fieldName 属性名
* @param obj
* @return
*/
public static Object invokeGet(String fieldName,Object obj) {
try {
Class c = obj.getClass();
Method m = c.getDeclaredMethod("get"+StringUtils.firstChar2UpperCase(fieldName), null);
return m.invoke(obj, null);
} catch (Exception e) {
e.printStackTrace();
return null;
}
}

public static void invokeSet(Object obj,String columnName,Object columnValue) {
Method m;
try {
if (columnValue!=null) {
m = obj.getClass().getDeclaredMethod("set"+StringUtils.firstChar2UpperCase(columnName),
columnValue.getClass());
m.invoke(obj, columnValue);
}
} catch (Exception e) {
e.printStackTrace();
}
}
}

StringUtils类

StringUtils类封装了字符串常用的操作,主要提供了将目标字符串首字母变为大写的方法,这个方法会经常用到。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
/**
* 封装了字符串常用的操作
* @author jinqi
*
*/
public class StringUtils {

/**
* 将目标字符串首字母变为大写
* @param str 目标字符串
* @return 首字母变为大写的字符串
*/
public static String firstChar2UpperCase(String str) {
//目标:abcd-->Abcd
//abcd-->ABCD-->Abcd
return str.toUpperCase().substring(0,1)+str.substring(1);
}
}

配置文件

db.properties

在这里由于我是进行的MySql数据库连接,因此驱动类都是MySql的,如果需要接入Oracle数据库的,修改配置信息以及导入驱动即可。

1
2
3
4
5
6
7
8
9
10
driver=com.mysql.cj.jdbc.Driver
url=jdbc:mysql://localhost:3306/sorm?useUnicode=true&characterEncoding=utf-8&useSSL=false
user=root
pwd=Jq576163960
usingDB=mysql
srcPath=/Users/jinqi/Desktop/SORM/src
poPackage=com.jinqi.po
queryClass=com.jinqi.sorm.core.MySqlQuery
poolMinSize:10
poolMaxSize:100

使用说明

至此,整个框架的结构都已经总结结束了,这个框架理解起来并不难,后续还可以加入其它如XML解析等,让它更加优化。最后说一下使用说明,需要导入SROM1.0的jar包以及MySql连接的jar包,注意导入的MySql连接的jar包要与本地安装的MySql版本一致,否则会连接失败,更多细节可以查看API文档以及框架源码。

原文作者:金奇

原文链接:https://www.rossontheway.com/2019/02/28/java回顾10/

发表日期:February 28th 2019, 12:00:00 am

更新日期:March 21st 2019, 9:33:21 am

版权声明:本文采用知识共享署名-非商业性使用 4.0 国际许可协议进行许可,除特别声明外,转载请注明出处!

CATALOG
  1. 1. 前言
  2. 2. 具体实现
    1. 2.1. 核心架构
      1. 2.1.1. bean包
        1. 2.1.1.1. ColumnInfo类
        2. 2.1.1.2. Configuration类
        3. 2.1.1.3. JavaFieldGetSet类
        4. 2.1.1.4. TableInfo类
      2. 2.1.2. core包
        1. 2.1.2.1. CallBack接口
        2. 2.1.2.2. DBManager类
        3. 2.1.2.3. MySqlQuery类
        4. 2.1.2.4. MySqlTypeConvertor类
        5. 2.1.2.5. Query抽象类
        6. 2.1.2.6. QueryFactory类
        7. 2.1.2.7. TableContext类
        8. 2.1.2.8. TypeConvertor接口
      3. 2.1.3. pool包
        1. 2.1.3.1. DBConnPool类
      4. 2.1.4. utils包
        1. 2.1.4.1. JavaFileUtils类
        2. 2.1.4.2. JDBCUtils类
        3. 2.1.4.3. ReflectUtils类
        4. 2.1.4.4. StringUtils类
    2. 2.2. 配置文件
      1. 2.2.1. db.properties
  3. 3. 使用说明