众所周知,ORM框架有很多,例如Hibernate,MyBatis,还有BeetlSQL等等,里面获取有很多我们不需要的功能,本系列博客主要教大家如何写一个简单的ORM框架
这个ORM框架主要有以下功能:
1. 生成JavaBean代码
2. 通过JavaBean来实现增删查改
我们这次先讲如何生成JavaBean代码
主要有以下几个步骤:
1. 获取数据库连接
2. 获取表的信息
3. 将数据库的类型转为Java类型
4. 生成代码文件
1.获取数据库连接
先写配置文件
#数据库驱动
driver=com.mysql.jdbc.Driver
#数据库url
url=jdbc\:mysql\://localhost\:3306/shiro
#数据库用户名
user=root
#数据库密码
pwd=root
#使用数据库类型
usingDB=mysql
#项目src地址
srcPath=/home/xjk/IdeaProjects/SORM/src
#生成JavaBean包名
poPackage=com.jk.po
我们还需要一个配置信息的管理类
public class Configuration {
private String driver;
private String url;
private String user;
private String pwd;
private String usingDB;
private String srcPath;
private String poPackage;
public Configuration() {
}
/**
* 省略setter和getter
*/
}
然后需要一个数据库管理类,来使用配置信息生成数据库连接
private static Configuration conf;
static {
Properties properties=new Properties();
try {
properties.load(Thread.currentThread().getContextClassLoader().getResourceAsStream("db.properties"));
} catch (IOException e) {
e.printStackTrace();
}
conf=new Configuration();
conf.setDriver(properties.getProperty("driver"));
conf.setPoPackage(properties.getProperty("poPackage"));
conf.setSrcPath(properties.getProperty("srcPath"));
conf.setPwd(properties.getProperty("pwd"));
conf.setUser(properties.getProperty("user"));
conf.setUrl(properties.getProperty("url"));
conf.setUsingDB(properties.getProperty("usingDB"));
}
public static Connection getConn(){
try {
//要求JVM查找并加载指定的数据库驱动
Class.forName(conf.getDriver());
return DriverManager.getConnection(conf.getUrl(),conf.getUser(),conf.getPwd());
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (SQLException e) {
e.printStackTrace();
}
return null;
}
public static Configuration getConf(){
return conf;
}
}
2.获取表的信息
我们使用TableContext来装载所有表,使用TableInfo来装载每个表的信息,ColumnInfo来装载每个字段的信息,我们先从最小单位开始
ColumnInfo
public class ColumnInfo {
/**
* 字段名
*/
String name;
/**
* 字段的数据类型
*/
String dataType;
/**
* 字段的键类型(0:普通键,1:主键,2:外键)
*/
int keyType;
public ColumnInfo() {
}
/**
* 省略setter和getter
*/
}
TableInfo
public class TableInfo {
/**
* 表名
*/
private String tname;
/**
* 所有字段信息
*/
private Map columns;
/**
* 唯一主键(目前我们只能处理表中只有一个主键的情况)
*/
private ColumnInfo onlyPriKey;
/**
* 联合主键
*/
private List priKeys;
public TableInfo() {
}
/**
* 省略setter和getter
* /
}
TableContext
public class TableContext {
/**
* 表名为key,表结构为value
*/
public static Map tables=new HashMap<>();
private 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 HashMap(),new ArrayList());
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()){
String columnName=set2.getString("COLUMN_NAME");
System.out.println(columnName);
ColumnInfo ci2=ti.getColumns().get(columnName);
ci2.setKeyType(1);//设为主键
ti.getPriKeys().add(ci2);
}
if (ti.getPriKeys().size()>0){//取唯一主键,如果是联合主键,则为空
ti.setOnlyPriKey(ti.getPriKeys().get(0));
}
}
} catch (SQLException e) {
e.printStackTrace();
}
}
}
3.将数据库的类型转为Java类型
为了适配多种数据库,我们先定义一个类型转换器的接口
public interface TypeConvertor {
/**
* 将数据库类型转换为java数据类型
* @param column 数据库字段的类型
* @return java的数据类型
*/
public String databaseType2JavaType(String column);
/**
* 将java数据类型转换为数据库类型
* @param column java的数据类型
* @return 数据库字段的类型
*/
public String javaType2DatabaseType(String column);
}
我们这里只展示MySQL类型转换器的写法
public class MySQLTypeConvertor implements TypeConvertor{
@Override
public String databaseType2JavaType(String column) {
switch (column.toLowerCase()){
case "varchar":
case "char":return "String";
case "smallint":
case "int":
case "tinyint":return "Integer";
case "bigint":return "Long";
case "double":return "Double";
case "float":return "Double";
case "clob":return "java.sql.Clob";
case "blob":return "java.sql.Blob";
case "date":return "java.sql.Date";
case "time":return "java.sql.Time";
case "timestamp":return "java.sql.Timestamp";
default:return null;
}
}
@Override
public String javaType2DatabaseType(String column) {
//这里后面再完善
return null;
}
}
4.生成代码文件
我们需要用一个JavaFieldGetSet来装载MySQL每个字段对应的属性还有setter和getter
public class JavaFieldGetSet {
/**
* 属性源码信息,如:private int id;
*/
private String fieldInfo;
/**
* get方法的源码信息,如:public int getId();
*/
private String getInfo;
/**
* set方法的源码信息,如:public void setId(int id);
*/
private String setInfo;
public JavaFieldGetSet() {
}
/**
* 省略setter和getter
* /
}
在转换的时候我们还要把名字格式从下划线分隔转为小驼峰,我们需要一个字符安转换工具
public class StringUtils {
/**
* 将下划线转为大驼峰
* @param str 目标字符串
* @return 变为大驼峰的字符串
*/
public static String underlineToBigCamel(String str){
return underlineToSmallCamel(str.toUpperCase().substring(0,1)+str.substring(1));
}
/**
* 将下划线转为小驼峰
* @param str 目标字符串
* @return 变为小驼峰的字符串
*/
public static String underlineToSmallCamel(String str){
if (str==null||"".equals(str.trim())){
return "";
}
int len=str.length();
StringBuilder sb=new StringBuilder(len);
for (int i = 0; i < len; i++) {
char c=str.charAt(i);
if (c=='_'){
if (++i
代码生成的工具
public class JavaFileUtils {
/**
* 根据字段信息生成java属性信息,如:var username-->private String username;相应的set和get方法源码
* @param column 字段信息
* @param convertor 类型转换器
* @return java属性的set/get方法
*/
public static JavaFieldGetSet createFieldGetSetSRC(ColumnInfo column, TypeConvertor convertor){
JavaFieldGetSet jfgs=new JavaFieldGetSet();
//将字段转为java属性
String javaFiledType= convertor.databaseType2JavaType(column.getDataType());
String colunmName=StringUtils.underlineToSmallCamel(column.getName());
//生成字段信息
jfgs.setFieldInfo("\tprivate "+javaFiledType+" "+colunmName+";\n");
//生成getter
StringBuilder getSrc=new StringBuilder();
getSrc.append("\tpublic "+javaFiledType+" get"+StringUtils.underlineToBigCamel(column.getName())+"(){\n");
getSrc.append("\t\treturn "+colunmName+";\n");
getSrc.append("\t}\n");
jfgs.setGetInfo(getSrc.toString());
//生成setter
StringBuilder setSrc=new StringBuilder();
setSrc.append("\tpublic void set"+StringUtils.underlineToBigCamel(column.getName())+"("+javaFiledType+" "+colunmName+"){\n");
setSrc.append("\t\tthis."+colunmName+"="+colunmName+";\n");
setSrc.append("\t}\n");
jfgs.setSetInfo(setSrc.toString());
return jfgs;
}
/**
* 根据表信息生成java源码
* @param tableInfo 表信息
* @param convertor 数据类型转换器
* @return java类的源代码
*/
public static String createJavaSrc(TableInfo tableInfo,TypeConvertor convertor){
Mapcolumns=tableInfo.getColumns();
List javaFields=new ArrayList<>();
for (ColumnInfo columnInfo:columns.values()){
JavaFieldGetSet javaFieldGetSet=createFieldGetSetSRC(columnInfo,convertor);
javaFields.add(javaFieldGetSet);
}
StringBuilder src=new StringBuilder();
//生成package语句
src.append("package "+ DBManager.getConf().getPoPackage()+";\n");
//生成import语句
src.append("import java.sql.*;\n");
src.append("import java.util.*;\n\n");
//生成类声明语句
src.append("public class "+StringUtils.underlineToBigCamel(tableInfo.getTname())+"{\n");
//生成属性列表
for (JavaFieldGetSet javaFieldGetSet:javaFields){
src.append(javaFieldGetSet.getFieldInfo());
}
src.append("\n\n");
//生成get方法列表
for (JavaFieldGetSet javaFieldGetSet:javaFields){
src.append(javaFieldGetSet.getGetInfo());
}
src.append("\n\n");
//生成set方法列表
for (JavaFieldGetSet javaFieldGetSet:javaFields){
src.append(javaFieldGetSet.getSetInfo());
}
src.append("\n\n");
//生成结束符
src.append("}");
return src.toString();
}
/**
* 生成java文件
* @param tableInfo 表信息
* @param convertor 类型转换器
*/
public static void createJavaPoFile(TableInfo tableInfo,TypeConvertor convertor){
//获取源码
String src=createJavaSrc(tableInfo,convertor);
String srcPath=DBManager.getConf().getSrcPath()+"/";
//将包名转换为文件名,然后和srcPath拼接
String packagePath=DBManager.getConf().getPoPackage().replace(".","/");
File f=new File(srcPath+packagePath);
if (!f.exists()){
f.mkdirs();
}
BufferedWriter bw=null;
try {
//将源码写入文件
bw=new BufferedWriter(new FileWriter(f.getAbsoluteFile()+"/"+StringUtils.underlineToBigCamel(tableInfo.getTname())+".java"));
bw.write(src);
} catch (IOException e) {
e.printStackTrace();
}finally {
if (bw!=null){
try {
bw.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
/**
* 更新表结构
*/
public static void updateJavaPOFile(){
Maptables=TableContext.tables;
for(TableInfo tableInfo:tables.values()){
createJavaPoFile(tableInfo,new MySQLTypeConvertor());
}
}
}
当我们调用JavaFileUtils的updateJavaPOFile方法时,便会在相应的目录生成该数据库里所有表对象的JavaBean