前言:读完本片文章大约20分钟
主要分为如下几个包,和以下几个类,接口(一级为包,二级(普通颜色为类,黄色为接口),加粗为说明)
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
/**
* 数据库驱动
*/
private String driver;
/**
* 连接数据库url
*/
private String url;
/**
* 数据库用户名
*/
private String username;
/**
* 数据库密码
*/
private String password;
/**
* 项目src绝对路径
*/
private String srcPath;
/**
* 生成的表对象的包路径
*/
private String pojoPackage;
后面我们将会通过数据库的元数据获取表,以及表中的字段,所以,我们需要对应的pojo,如下
字段:(set,get等方法省略)
/**
* @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等方法省略)
/**
* @author GXM www.guokangjie.cn
* @date 2019/5/15
*
* 对应数据库中的表结构
*/
public class TableInfo {
/**
* 表的名称 例如 User
*/
private String name;
/**
* 表中的字段,使用map为方便后期获取
* 例如 map
*/
private Map<String,ColumnInfo> columns;
/**
* 表中的唯一主键
*/
private ColumnInfo onlyPriKey;
/**
* 联合主键即(多个字段组成的主键)
*/
private List<ColumnInfo> unionKey;
从前面的架构来说,连接的提供以及资源文件的映射都在DBManager中处理,因为资源文件映射只需要一次即可,所以放在static中(只会加载一次),并且向外提供了获取连接的静态方法,以及Configuration静态方法,方便外界的操作!
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;
}
}
这部分知识需要用到数据库的元数据,不理解的请参考:JDBC元数据操作(一)-- DatabaseMetaData接口详解
这部分我们需要在tableContext类中完成,如下
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
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
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;
测试代码:
public static void main(String[] args) {
System.out.println(TableContext.getAllTables());
}
结果:(这样可能不舒服,推荐大家debug一下,在debug中看的更明显)
{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}]}}
因为这里主要使用mysql所以就写这一部分即可,而且这里并不做所有数据的转化!
这样,方便以后的使用,与扩展
类MysqlType:
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";
}
写一个类MySqlTypeConvertor继承TypeConvertor
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 "";
}
}
上一部分我们已经做到了,可以根据数据库的表获取其中的元数据了,接下就是根据元数据完成pojo,说白了,就是拼接字符串,并生成文件
StringUtils:
/**
* 将字符串的首字母大写
* @param src
* @return
*/
public static String toUpperCaseHeadChar(String src){
return src.toUpperCase().substring(0,1)+src.substring(1);
}
JavaFieldSetGet (set,get方法省略)
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;
}
}
generateJavaFieldSetGet 每一个pojo都有属性和set,get方法,我们生成说白了就是字符串
该方法 测试结果如图:
generateJavaSrc 根据上面的方法我们可以完成一个属性的字符串所有功能,所以我们这里再将表的数据也拼接成字符串,合并最总变为一个java的pojo(只不过是字符串)
该方法 测试结果如图:
generatePOJOFile 对外提供方法生成pojo文件
其代码如下:
/**
* @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类中获取了所有的数据库源信息(第一部分与第二部分),而在第三部分我们也完成了根据tableInfo来生成pojo的(.java文件),但是JavaFileUtils总归是一个工具类,我们将在TableContext对外提供生成的方法,即在TableContext类中添加方法
/**
* 生成或者跟新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");
}
}