SORM_实现对象和SQL语句自动映射的小框架

如有错误,请多指教!

在学习了JDBC之后,这里做一个简单的总结,在本文中主要是写一个小框架生成一个Jar包,在后续其他的工程中,若要使用到数据库的一些操作(增删改查),导入此jar包可以方便数据库的操作

简单描述一下,在这个项目下,主要分为5个部分:
1)配置文件:ad.properties

文件名文件描述
ad.properties文件内容中有数据库链接地址,连接池最大连接数和最小连接数,读取数据库表信息后生成表对应的类的路径,数据库账号,数据库密码,数据库类型等(这里用的是mysql)

2).com.sorm.utils,此包主要是一些工具类;

工具类类描述
JDBCUtils封装常用JDBC操作,给sql语句设参,因为是采用PreparedStatement,所以需要进行设参操作
JavaFileUtils封装java文件(源码)操作),也就是根据数据库表格信息生成对应的类
ReflectUtils封装常用反射操作.比如通过反射机制,可以调用javabean对象不同类属性的set/get方法 ,达到获取bean对象不同类属性的值,或者给bean对象设值
StringUtils字符串工具类 ,此类有一个方法:字母大小写转换,首字母大写,其余字母小写

3).com.sorm.pool,此包含有线程池管理的类

类描述
ConnectionPool管理数据库Connection对象的连接池

4).com.sorm.bean,此包中类的描述如下:

类描述
ColumnInfo封装数据库表中一个字段的信息(字段类型,字段名,键类型)
Configuration封装配置文件信息,在此项目中有配置文件:db.properties
JavaFieldGetSet封装了java类的属性/方法的get/set方法的源码方便后期通过表结构自动生成相应类的源码,源码的生成路径就是配置文件的poPackage
TableInfo封装一张表的信息,比如:表名,主键,存放列信息的容器等

5).com.sorm.core,项目的核心包

类/接口类描述
DBManager读取配置信息,数据库连接对象的管理,连接池中的数据库连接对象也从此类的静态方法获取
TypeConvertor(接口)负责类型转换,数据库类型转换成Java数据类型,java数据类型转换成数据库类型
MysqlTypeConvertorTypeConvertor接口的实现类,主要负责数据库字段类型的转换,转换为java数据类型
TableContext负责获取管理数据库所有表结构和类结构的关系,并可以根据表结构生成类结构
Query负责查询(对外提供服务的核心抽象类), 含有常规的增删改查的方法,这些方法大部分是mysql和oracle通用的(因为mysql和oracle存在部分语法差异)
MysqlQuery继承Query,重写了父类的部分方法,
CallBack(接口)配合Query类的模板方法使用,因为每个查询方法有相同的代码,也有不同的代码细节,此接口的实现类需要实现那些不同的代码细节
QueryFactory负责根据配置信息创建Query对象,通过Query实例对象直接调用相应的增删改查的方法对数据库进行操作

上面描述的5个部分,框架如下图,其中com.sorm.po可以不用建,在操作数据时会自动根据表信息生成对应的类,这些类都存放在com.sorm.po包下;com.sorm.test可以不用管,这个是我在测试的时候建的;com.sorm.vo包下是针对相对较复杂的查询语句定制的javabean类,比如多表查询…好了话不多说,项目框架如下:

在这里插入图片描述

好了,上面描述了这个项目的用途和项目的结构,接下来可以开始撸代码了

<1>先从com.sorm.bean核心包下的Configuration开始吧,这个类可以封装项目的配置文件的信息,类中的成员属性和配置文件信息一一对应,在后面的操作中可以把读取到的配置信息通过set方法把配置信息封装到Configuration中,即便后项目中修改了数据,也不需要修改代码

package com.sorm.bean;
/**
 * 封装配置文件信息
 * @author 
 *
 */
public class Configuration {
	/**
	 * 连接池的最小连接数
	 */
	private int poolMinSize;
	/**
	 * 连接池的最大连接数
	 */
	private int poolMaxSize;
	/**
	 * 数据库驱动类
	 */
	private String driver;
	/**
	 * 数据库url
	 */
	private String url;
	/**
	 * 数据库用户名
	 */
	private String user;
	/**
	 * 数据库密码
	 */
	private String pwd;
	/**
	 * 数据库类型(mysql/oracle/db2)
	 */
	private String databaseType;
	/**
	 * 项目的源码路径
	 */
	private String srcPath;
	
	/**
	 * 此项目查询类的路径
	 */
	private String mysqlQueryClass;
	
	public String getMysqlQueryClass() {
		return mysqlQueryClass;
	}
	public void setMysqlQueryClass(String mysqlQueryClass) {
		this.mysqlQueryClass = mysqlQueryClass;
	}
	/**
	 * 扫描生成java类的包(po:persistence object持续对象)
	 */
	private String poPackage;
	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 getDatabaseType() {
		return databaseType;
	}
	public void setDatabaseType(String databaseType) {
		this.databaseType = databaseType;
	}
	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;
	}
	public Configuration(String driver, String url, String user, String pwd, String databaseType, String srcPath,
			String poPackage) {
		super();
		this.driver = driver;
		this.url = url;
		this.user = user;
		this.pwd = pwd;
		this.databaseType = databaseType;
		this.srcPath = srcPath;
		this.poPackage = poPackage;
	}
	public Configuration() {
	}
	
	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;
	}
}

<2>数据库连接池管理:ConnectionPool

package com.sorm.pool;

import java.sql.Connection;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;

import com.sorm.core.DBManager;

/**
 * Connection对象连接池
 * @author 
 *
 */
public class ConnectionPool {
	/**
	 * 连接池最小连接数
	 */
	private final static int POOL_MIX = DBManager.getCon().getPoolMinSize();
	/**
	 * 连接池最大连接数
	 */
	private final static int POOL_MAX = DBManager.getCon().getPoolMaxSize();
	
	private static  List<Connection> list;
	
	
	
	public ConnectionPool() {
		initialPool();
	}

	/**
	 * 存放数据库连接对象的list容器
	 * 需要建立数据库连接时,从容器中取出最后一个Connection对象,同时从容器中删除这个对象
	 * 需要断开连接时,把这个connection对象放入list容器,如果list容器数量超出POOL_MAX,
	 * 就真的释放这个连接对象,也就是调用Connection的close方法进行真正意义上的释放
	 * for(int i = list.size();i<POOL_MIX;i++) {
	 */
	public void initialPool() {
		if(list == null) {
			list = new ArrayList<Connection>();
		}
		while(list.size()<POOL_MIX) {
			list.add(DBManager.createConnection());
			
		}
		System.out.println("初始化连接池,池中连接对象数:"+list.size());
	}
	
	/**
	 * 需要建立数据库连接时,调用此方法,从连接池获取一个Connection对象即可
	 * @return
	 */
	public synchronized Connection getConnection() {
		Connection lastConn = list.get(list.size()-1);
		list.remove(list.size()-1);
		return lastConn;
	}
	
	/**
	 * 需要断开连接时,把这个connection对象放入list容器,如果list容器数量超出POOL_MAX,
	 * 就真的释放这个连接对象,也就是调用Connection的close方法进行真正意义上的释放
	 * @param conn
	 */
	public static void closeConnection(Connection conn) {
		if(list.size()>POOL_MAX) {
			try {
				conn.close();
			} catch (SQLException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}else {
			list.add(conn);
		}
	}
}

<3>封装配置信息,数据库连接对象的管理DBManager

package com.sorm.core;
/**
 * 根据配置信息,维持连接对象的管理
 * @author 
 *
 */

import java.io.IOException;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.Properties;

import com.sorm.bean.Configuration;
import com.sorm.pool.ConnectionPool;

public class DBManager {
	
	private static Configuration conf;
	private static Properties pros;
	private static ConnectionPool pool = null;
	static {
		/**
		 * 加载指定的配置文件
		 */
		pros = new Properties();
		try {
			pros.load(Thread.currentThread().getContextClassLoader().getResourceAsStream("db.properties"));
			/**
			 * 把获取到的配置信息添加到Configuration对象中
			 */
			conf = new Configuration();
			conf.setDatabaseType(pros.getProperty("databaseType"));
			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.setMysqlQueryClass(pros.getProperty("queryClass"));
			conf.setPoolMaxSize(Integer.parseInt(pros.getProperty("poolMaxSize")));
			conf.setPoolMinSize(Integer.parseInt(pros.getProperty("poolMinSize")));
			
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		
		//加载TableContext
		Class clazz = TableContext.class;
		
	}
	
	/**
	 * 测试用的,测试是否能获取响应配置文件数据
	 * @return
	 */
	public static String getMysqlDriver() {
		return pros.getProperty("driver");
	}
	
	/**
	 * 返回Configuration对象
	 * @return
	 */
	public static Configuration getCon() {
		return conf;
	}
	
	/**
	 * 建立数据库连接
	 * @return
	 * @throws Exception
	 */
	public static Connection getConnection() throws Exception {
		if(pool==null) {
			pool  = new ConnectionPool();
		}
		return pool.getConnection();
	}
	
	/**
	 * 创建数据库连接,当连接池中需要获取connection对象时,从此方法获取
	 * @return
	 * @throws Exception
	 */
	public static Connection createConnection()  {
		try {
			Class.forName(conf.getDriver());
			return DriverManager.getConnection(conf.getUrl(), conf.getUser(), 					    conf.getPwd());
		} catch (Exception e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
			return null;
		}
	}
	
	/**
	 *关闭相应资源, 重载
	 * @param r
	 * @param ps
	 * @param c
	 */
	public static void close(ResultSet r,PreparedStatement ps,Connection c) {
		try {
			if(r!=null)
			r.close();
		} catch (SQLException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		try {
			if(ps!=null)
				ps.close();
		} catch (SQLException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		try {
			if(c!=null)
				ConnectionPool.closeConnection(c);
		} catch (Exception e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}
	public static void close(PreparedStatement ps,Connection c) {
		try {
			if(ps!=null)
				ps.close();
		} catch (SQLException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		try {
			if(c!=null)
				ConnectionPool.closeConnection(c);
		} catch (Exception e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}
	
	public static void close(ResultSet r,PreparedStatement ps) {
		try {
			if(r!=null)
			r.close();
		} catch (SQLException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		try {
			if(ps!=null)
				ps.close();
		} catch (SQLException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}
	public static void close(ResultSet r,Statement s) {
		try {
			if(r!=null)
				r.close();
		} catch (SQLException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		try {
			if(s!=null)
				s.close();
		} catch (SQLException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}
}

<4>封装表中一个字段的信息(字段类型,字段名,键类型)

package com.sorm.bean;
/**
 * 封装表中一个字段的信息(字段类型,字段名,键类型)
 * @author 
 *
 */
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() {};
	
}

<5>封装一张表的信息

package com.sorm.bean;

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

/**
 * 封装一张表的信息
 * @author 
 *
 */
public class TableInfo {
	/**
	 * 表名
	 */
	private String name;
	/**
	 * 所有字段信息
	 */
	private Map<String,ColumnInfo> columns;
	/**
	 * 唯一主键
	 */
	private ColumnInfo onlyPriKey;
	/**
	 * 联合主键
	 */
	private List<ColumnInfo> priKeys;
	
	public String getName() {
		return name;
	}
	public void setName(String name) {
		this.name = name;
	}
	public Map<String, ColumnInfo> getColumns() {
		return this.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 List<ColumnInfo> getPriKeys() {
		return this.priKeys;
	}
	public void setPriKeys(List<ColumnInfo> priKeys) {
		this.priKeys = priKeys;
	}
	public TableInfo(String name, Map<String, ColumnInfo> columns, ColumnInfo onlyPriKey, List<ColumnInfo> priKeys) {
		super();
		this.name = name;
		this.columns = columns;
		this.onlyPriKey = onlyPriKey;
		this.priKeys = priKeys;
	}
	public TableInfo() {}
	
	public TableInfo(String tableName, 
					Map<String, ColumnInfo> hashMap,
					List<ColumnInfo> arrayList) {
		this.name = tableName;
		this.columns = hashMap;
		this.priKeys = arrayList;
		
	};
	
}

<6>获取管理数据库所有表结构和类结构的关系,并根据表结构生成类结构:TableContext

package com.sorm.core;

import java.sql.Connection;
import java.sql.DatabaseMetaData;
import java.sql.ResultSet;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;

import com.sorm.bean.ColumnInfo;
import com.sorm.bean.TableInfo;
import com.sorm.pool.ConnectionPool;
import com.sorm.utils.JavaFileUtils;
import com.sorm.utils.StringUtils;

/**
 * 负责获取管理数据库所有表结构和类结构的关系,并可以根据表结构生成类结构
 * @author 
 *
 */
public class TableContext {
	/**
	 * 表名为key,表信息对象为value
	 */
	public static Map<String,TableInfo> tables = new HashMap<String,TableInfo>();
	/**
	 * 将po的class对象和表信息关联起来,便于重用
	 */
	public static Map<Class,TableInfo> poClassTable = new HashMap<Class,TableInfo>();
	
	private TableContext() {};
	
	static {
		try {
//			Connection conn = ConnectionPool.getConnection();
			Connection conn = DBManager.getConnection();
			System.out.println("打印日志Catalog<数据库>:"+conn.getCatalog());
			DatabaseMetaData dbmd = conn.getMetaData();
			/**
			 * 检索给定目录中可用表的描述。
			 * 找出数据库中可用的表
			 */
			
			ResultSet tableSet = dbmd.getTables(conn.getCatalog(), "%", "%", new String[]{"TABLE"});//如果把conn.getCatalog()替换为null,则是获取所有数据库的表信息(数据库自带的数据库除外)
			while(tableSet.next()) {
				String tableName = (String) tableSet.getObject("TABLE_NAME");
				TableInfo tableInfo = new TableInfo(tableName, new HashMap<String,ColumnInfo>(), new ArrayList<ColumnInfo>());
				//System.out.println(tableInfo.getName());
				tables.put(tableName, tableInfo);
				
				/**
				 * 获取表中所有的列
				 * 往表里面添加列信息对象,也就是把列对象(ColumnInfo)添加到TableInfo的Map<String,ColumnInfo> columns中;
				 */
				ResultSet set = dbmd.getColumns(null, "%", tableName, "%");
				while(set.next()) {
					ColumnInfo ci = new ColumnInfo(set.getString("COLUMN_NAME"), set.getString("TYPE_NAME"), 0);
					//System.out.println(ci.getDataType());
					tableInfo.getColumns().put(set.getString("COLUMN_NAME"), ci);
					
				}
				
				/**
				 * 获取表中的主键
				 * 其实在上一步获取列对象操作中,已经把表中的所有列都添加到了TableInfo的Map<String,ColumnInfo> columns中
				 * 现在只需要在columns容器中根据列名就可以获取对应主键的ColumnInfo对象
				 * 并将此对象添加到TableInfo的联合主键List容器中
				 */
				ResultSet set2 = dbmd.getPrimaryKeys(null, "%", tableName);//查询表中的主键
				while(set2.next()) {
					ColumnInfo ciOfPriKey = tableInfo.getColumns().get(set2.getString("COLUMN_NAME"));
					ciOfPriKey.setKeyType(1);//设为主键类型,之前是0,也就是普通键
					tableInfo.getPriKeys().add(ciOfPriKey);
				}
				
				
				/**
				 * 取唯一主键
				 */
				if(tableInfo.getPriKeys().size()>0) {
					tableInfo.setOnlyPriKey(tableInfo.getPriKeys().get(0));
				}
				
			}
			
		} catch (Exception e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		
		//在执行静态代码块的时候就把在com.sorm.po包下自动更新数据库中表对应的类信息
		updateClass();
		//在执行静态代码块的时候就把com.sorm.po包下类的Class对象和tableinfo一一对应,存放到poClassTable容器
		MatchPoClassAndTableInfo();
		
	}
	
	/**
	 * 更新com.sorm.po包下的类文件
	 */
	public static void updateClass() {
//			Map<String,TableInfo> table = TableContext.tables;
//			TableInfo t = table.get("studentstable");
		for(TableInfo c:tables.values()) {
			JavaFileUtils.createPoJavaFile(c, new MysqlTypeConvertor());
		}
	}
	
	/**
	 * 把com.sorm.po包下的类的class对象和类对应的tableinfo对应起来,并放入poClassTable容器
	 */
	public static void MatchPoClassAndTableInfo() {
		for(TableInfo t:tables.values()) {
			try {
				Class c = Class.forName(DBManager.getCon().getPoPackage()+"."+StringUtils.lowerToCapital(t.getName()));
				poClassTable.put(c, t);
			} catch (ClassNotFoundException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}
	}
}

<7>在上面的Tablecontext类中有几个陌生不知名的类:StringUtils,JavaFileUtils,MysqlTypeConvertor;这三个类在文章的开头有描述,接下来我们去实现它,首先从StringUtils开始吧
StringUtils

/**
 * 封装常用字符串操作
 * @author 
 *
 */
public class StringUtils {
	/**
	 * 字母大小写转换,首字母大写,其余字母小写
	 * @param str
	 * @return
	 */
	public static String lowerToCapital(String str) {
		//首字母大写,其余字母小写
		return str.toUpperCase().substring(0,1)+str.substring(1);
	}
}

<8>数据库和java数据类型的转换,在com.sorm.core包下先建一个接口TypeConvertor,然后其实现类去实现数据库数据类型到java数据类型的转换/java数据类型到数据库类型转换,在本项目中未用到java数据类型到数据库类型转换
TypeConvertor

package com.sorm.core;
/**
 * 负责类型转换
 * 数据库类型转换成Java数据类型
 * java数据类型转换成数据库类型
 * @author 
 *
 */
public interface TypeConvertor {
	
	/**
	 * 数据库类型转换成Java数据类型
	 * @param databaseType 数据库数据类型
	 * @return
	 */
	public String datebaseTypeToJavaType(String databaseType);
	
	/**
	 * java数据类型转换成数据库类型
	 * @param javaType  java数据类型
	 * @return
	 */
	public String javaTypeToDatebaseType(String javaType);
	
}

<9>通过MysqlTypeConvertor实现TypeConvertor接口达到数据库数据类型到java数据类型的转换
MysqlTypeConvertor

package com.sorm.core;
/**
 * TypeConvertor接口的实现类
 * 主要负责数据库字段类型的转换,转换为java数据类型
 * @author 
 *
 */
public class MysqlTypeConvertor implements TypeConvertor {

	@Override
	public String datebaseTypeToJavaType(String databaseType) {
		if(databaseType.equals("VARCHAR")||databaseType.equals("VAR")) {
			return "String";
		}else if(databaseType.equals("TINYINT")
				||databaseType.equals("SMALLINT")
				||databaseType.equals("MEDIUMINT")
				||databaseType.equals("INT")
				||databaseType.equals("INTEGER")
				) {
			return "Integer";
		}else if(databaseType.equals("BIGINT")) {
			return "Long";
		}else if(databaseType.equals("FLOAT")||databaseType.equals("DOUBLE")) {
			return "Double";
		}else if(databaseType.equals("TEXT")
				||databaseType.equals("TINYTEXT")
				||databaseType.equals("MEDIUMTEXT")
				||databaseType.equals("LONGTEXT")
				) {
			return "java.sql.Clob";
		}else if(databaseType.equals("TINYBLOB")
				||databaseType.equals("BLOB")
				||databaseType.equals("MEDIUMBLOB")
				||databaseType.equals("LONGBLOB")
				) {
			return "java.sql.Blob";
		}else if(databaseType.equals("DATE")) {
			return "java.sql.Date";
		}else if(databaseType.equals("TIME")) {
			return "java.sql.Time";
		}else if(databaseType.equals("TIMESTAMP")
				||databaseType.equals("DATETIME")) {
			return "java.sql.Timestamp";
		}
		return null;
	}

	@Override
	public String javaTypeToDatebaseType(String javaType) {
		// TODO Auto-generated method stub
		return null;
	}

}

<10>在获取数据库表信息后,我们的程序需要在com.sorm.po包下生成对应的java类,在后面操作中,对这些javabean对象进行封装数据,通过javabean对象和相应的数据库操作方法就可以完成对数据库的操作;现在的问题是如何根据表信息生成对应的java类,那通过这个JavaFieldGetSet类和JavaFileUtils类就可以完成java类的源码生成,可能上面的描述有问题,那就直接开始代码吧
JavaFieldGetSet

package com.sorm.bean;
/**
 * 封装了java类的属性/方法的get/set方法的源码
 * 方便后期通过表结构自动生成相应类的源码,源码的生成路径就是配置文件的poPackage
 * @author 
 *
 */
public class JavaFieldGetSet {
	//属性的源码信息 如:private String name;
	private String fieldInfo;
	//get方法的码信息 如:public String getName(){return this.name};
	private String getInfo;
	//set方法的源码信息 如:public void setName(String name){this.name = name};
	private String setInfo;
	
	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() {};
	
	@Override
	public String toString() {
		System.out.println(fieldInfo);
		System.out.println(getInfo);
		System.out.println(setInfo);
		return super.toString();
	}
}

<11>接下来完成JavaFileUtils,完成数据库表对应的java类的源码,其实就是通过字符串拼接的方式进行的,然后通过BufferWriter把字符串源码写入到指定文件就ok了
JavaFileUtils

package com.sorm.utils;

import java.io.BufferedWriter;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;

import com.sorm.bean.ColumnInfo;
import com.sorm.bean.Configuration;
import com.sorm.bean.JavaFieldGetSet;
import com.sorm.bean.TableInfo;
import com.sorm.core.DBManager;
import com.sorm.core.MysqlTypeConvertor;
import com.sorm.core.TableContext;
import com.sorm.core.TypeConvertor;

/**
 * 封装java文件(源码)操作
 * @author 
 *
 */
public class JavaFileUtils {
	/**
	 * 根据字段信息生成java类属性信息,和相应的get/set方法
	 * @param c column字段信息(数据库表中的列信息)
	 * @param t TypeConvertor数据类型转换器
	 * @return 返回JavaFieldGetSet对象
	 */
	public static JavaFieldGetSet createFieldGetSet(ColumnInfo c,TypeConvertor t) {
		//获取JavaFieldGetSet对象
		JavaFieldGetSet jfgs = new JavaFieldGetSet();
		
		
		
		//获取列的类型,并转换成java数据类型
		String type = t.datebaseTypeToJavaType(c.getDataType());
		//对实例对象的属性进行赋值,也就是把列名赋值给jfgs的属性变量
		jfgs.setFieldInfo("\tprivate "+type+" "+c.getName()+";\n");//如:private String name;
		
		//生成get方法的码信息 如:public String getName(){return this.name};
		StringBuilder str = new StringBuilder();
		str.append("\tpublic "+type+" get"+StringUtils.lowerToCapital(c.getName())+"()"+"{\n");
		str.append("\t\treturn this."+c.getName()+";\n");
		str.append("\t}\n");
		jfgs.setGetInfo(str.toString());
		
		//set方法的源码信息 如:public void setName(String name){this.name = name};
		StringBuilder str1 = new StringBuilder();
		str1.append("\tpublic void set"+StringUtils.lowerToCapital(c.getName())+"("+type+" "+c.getName()+"){\n");
		str1.append("\t\tthis."+c.getName()+"="+c.getName()+";\n");
		str1.append("\t}\n");
		jfgs.setSetInfo(str1.toString());
		
		return jfgs;
	}
	
	/**
	 * 根据表信息,生成对应的表结构的源码
	 * @param t 表信息对象
	 * @param ty 数据类型转换器,这里指数据库转java数据类型
	 * @return 返回类的源码字符串
	 */
	public static String createClass(TableInfo t,TypeConvertor ty) {
		StringBuilder str = new StringBuilder();
		str.append("package "+DBManager.getCon().getPoPackage()+";\n");
		str.append("import java.util.*;\n");
		str.append("import java.sql.*;\n\n");
		str.append("public class "+StringUtils.lowerToCapital(t.getName())+"{\n");
		/**
		 * 加入属性/get/set源码的操作
		 */
		//先取出数据库表中所有列的字段信息到map容器
		Map<String,ColumnInfo> map = t.getColumns();
		//list容器存放所有字段的jfgs对象,方便后面根据jfgs对象获取相应的源码(类属性/set/get)
		List<JavaFieldGetSet> list = new ArrayList<>();
		//遍历map,获取所有列的字段对象,再放入list
		for(String m:map.keySet()) {
			JavaFieldGetSet jfgs = createFieldGetSet(map.get(m), ty);
			list.add(jfgs);
		}
		//添加所有字段的类属性
		for(JavaFieldGetSet jfgs:list) {
			str.append(jfgs.getFieldInfo());
		}
		//添加所有属性的get方法
		for(JavaFieldGetSet jfgs:list) {
			str.append(jfgs.getGetInfo());
		}
		//添加所有属性的set方法
		for(JavaFieldGetSet jfgs:list) {
			str.append(jfgs.getSetInfo());
		}
		//添加空构造器
		str.append("\tpublic "+StringUtils.lowerToCapital(t.getName())+"(){}\n");
		//添加类的结尾
		str.append("}\n");
		
		return str.toString();
	}
	/**
	 * 1.先获取文件的路径
	 * 2.然后用io流把源码字符串写入到指定路径
	 * @param t
	 * @param ty
	 */
	public static void createPoJavaFile(TableInfo t,TypeConvertor ty) {
		
		String str = createClass(t, ty);
		
		String srcPath = DBManager.getCon().getSrcPath();
		String poPath = DBManager.getCon().getPoPackage();//"com.sorm.po需要把.转换为/"
		String poPath2= poPath.replaceAll("\\.", "/");//括号内使用的正则表达式
		System.out.println("打印日志<源码路径>:"+srcPath+"/"+poPath2);
		
		File f = new File(srcPath+"/"+poPath2);
		//如果f不存在,就创建这个目录
		if(!f.exists()) {
			f.mkdirs();
		}
		
		BufferedWriter bw = null;
		try {
			bw = new BufferedWriter(new FileWriter(f.getAbsoluteFile()+"/"+
									StringUtils.lowerToCapital(t.getName())+
									".java"));
			bw.write(str);
			bw.flush();
			System.out.println("****源码已写入:"+StringUtils.lowerToCapital(t.getName())+".java****");
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}finally {
			if(bw!=null) {
				try {
					bw.close();
				} catch (IOException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
			}
	}
}

<12>工具类,通过反射,调用javabean对象对应的set/get方法
ReflectUtils

package com.sorm.utils;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

import com.sorm.bean.ColumnInfo;
import com.sorm.bean.TableInfo;
import com.sorm.core.TableContext;

/**
 * 封装常用反射操作
 * @author 
 *
 */
public class ReflectUtils {
	
	/**
	 * invokeGet方法通过反射机制,可以生成不同类属性的get方法,然后调用当前类对象的get方法获取对应属性的值
	 * @param fieldName 属性名
	 * @param obj 传入类对象
	 * @return 返回传入类的属性的值
	 */
	public static Object invokeGet(String fieldName,Object obj) {
		Class c = obj.getClass();
		
			/**
			 * 通过反射机制获取get方法
			 * 因为不同的表中,属性名不一样,需要通过字符串拼接来获取对应的get/set方法
			 * 然后通过相应的get/set方法来获取属性的值
			 */
			try {
				Method m = c.getMethod("get"+StringUtils.lowerToCapital(fieldName), null);
				return m.invoke(obj, null);
			} catch (Exception e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
				return null;
			} 
		
				
	}
	
	/**
	 * invokeSet方法通过反射机制,根据传入的不同名字来生成不同的set方法,然后调用set方法
	 * @param fieldName 传入的字符串名称
	 * @param obj 类对象
	 * @param value 传入的值
	 */
	public static void invokeSet(String fieldName,Object obj,Object value) {
	
		
		try {
			Method m = obj.getClass().getDeclaredMethod("set"+StringUtils.lowerToCapital(fieldName), value.getClass());
			m.invoke(obj, value);
		} catch (Exception e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		
	}
	
	
	
	
	
	
}

<13>接下来可以来实现我们核心包下的Query抽象类了,这个类把mysql和oracle操作手法相同的方法同一放在当前抽象类中,还可以定义因sql语句差异导致操作手法不同的抽象方法,让其子类去实现
Query

/**
 * 负责查询(对外提供服务的核心类)
 * @author 
 *
 */
@SuppressWarnings("all")
public abstract class Query implements Cloneable{
	
	/**
	 * 查询操作的模板方法
	 * @param sql sql语句
	 * @param clazz 需要封装到的javabean对象
	 * @param args sql语句的参数
	 * @param callback CallBack的实现类,在使用时可以使用匿名内部类
	 * @return
	 */
	public Object queryTemplate(String sql,Class clazz,Object[] args,CallBack callback) {
		Connection conn = null;
		PreparedStatement ps = null;
		ResultSet rs = null;
		try {
			conn =  DBManager.getConnection();
			ps = conn.prepareStatement(sql);
			//设参
			JDBCUtils.handleParams(ps, args);
			rs = ps.executeQuery();
			
			return callback.execute(conn, ps, rs);//实现不同细节的代码
		
		} catch (Exception e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}finally {
			DBManager.close(rs, ps, conn);
		}
		return null;
	}
	
	/**
	 * 执行一个DML语句
	 * @param sql  sql语句
	 * @param args  参数
	 * @return 执行sql语句后影响记录的行数
	 */
	public int executeDML(String sql,Object[] args) {
		Connection conn = null;
		PreparedStatement ps = null;
		try {
			int count = 0;
			conn = DBManager.getConnection();
			ps = conn.prepareStatement(sql);
			
			//sql语句设参
			JDBCUtils.handleParams(ps, args);
			//执行sql语句
			count = ps.executeUpdate();
			System.out.println("执行语句<日志>:"+ps);
			return count;
		} catch (Exception e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}finally {
			DBManager.close(ps, conn);
		}
		
		return 0;
	}
	
	/**
	 * 将一个对象存储到数据库中
	 * @param obj 需要储存的对象
	 */
	public void insert(Object obj) {
		Class c = obj.getClass();
		//将obj对象的属性值放入list容器,方便后期转换成数组,作为参数传入executeDML中
		List<Object> values = new ArrayList<>();
		//获取obj的所有属性fs数组
		Field[] fs = c.getDeclaredFields();
		//设定计数器,因为有几个值不为null的属性,就要传入几个对应的参数,在后边的遍历数组操作中,每有一个值不为null的属性,countNotNullField就要+1
		int countNotNullField = 0;
		
		/**
		 * 借助for循环,拼接sql语句
		 * insert into emp(id,name,info) values(?,?,?);
		 */
		StringBuilder sql = new StringBuilder();
		sql.append("insert into "+TableContext.poClassTable.get(c).getName()+"(");
		//遍历属性数组,同时获取值不为null的属性的值,把值放入values容器,同时拼接sql语句
		for(Field f:fs) {
			//利用ReflectUtils工具类的invokeGet方法获取对应属性的值,
			Object value = ReflectUtils.invokeGet(f.getName(), obj);
			if(value!=null) {
				values.add(value);
				countNotNullField++;
				sql.append(f.getName()+",");
			}
		}
		//上面拼接完成是这样的:insert into emp(id,name,info,需要把最后一个逗号进行替换为")"
		sql.setCharAt(sql.length()-1, ')');
		sql.append(" values(");
		for(int i=0;i<countNotNullField;i++) {
			sql.append("?,");
		}
		sql.setCharAt(sql.length()-1, ')');//到此拼接完成
		//调用executeDML方法执行insert语句
		executeDML(sql.toString(), values.toArray());
	}
	
	/**
	 * 删除clazz对象对应的表中的记录(指定主键值id的记录)
	 * @param clazz 和表对应的Class对象
	 * @param id  主键值
	 */
	public void delete(Class clazz,Object id) {
		//根据class对象获取类对应的tableInfo
		TableInfo tableInfo = TableContext.poClassTable.get(clazz);
		//获取主键信息
		ColumnInfo columnInfo = tableInfo.getOnlyPriKey();
		
		String sql = "delete from "+tableInfo.getName()+" where "+columnInfo.getName()+"=?";
		executeDML(sql, new Object[] {id});
	}
	
	/**
	 * 删除对象在数据库对应的记录
	 * @param obj 需要删除的对象记录
	 */
	public void delete(Object obj) {
		Class c = obj.getClass();
		//根据class对象获取类对应的tableInfo
		TableInfo tableInfo = TableContext.poClassTable.get(c);
		//获取主键信息
		ColumnInfo columnInfo = tableInfo.getOnlyPriKey();
		//通过ReflectUtils工具类的相应方法获取主键的值
		Object priKeyValue = ReflectUtils.invokeGet(columnInfo.getName(), obj);
		//调用delete(Class clazz, Object id)执行操作
		delete(c, priKeyValue);
	}
	
	/**
	 * 更新对象对应的记录
	 * forExample:update user set uname=?,id=?,salary=?
	 * @param obj 需要更新的对象
	 * @param columns  更新对象的列(一列或者多列)
	 * @return 返回更新后影响记录的行数
	 */
	public int update(Object obj,String[] columns) {
	//update emp set salary=?,name=? where id=?
		
		Class c = obj.getClass();
		List<Object> params = new ArrayList<>();
		TableInfo t = TableContext.poClassTable.get(c);
		ColumnInfo prikey = t.getOnlyPriKey();
		
		StringBuilder sql = new StringBuilder();
		sql.append("update "+t.getName()+" set ");
		
		Field[] fs = c.getDeclaredFields();
		for(String name:columns) {
			Object param = ReflectUtils.invokeGet(name, obj);
			params.add(param);
			sql.append(name+"=?,");
		}
		sql.setCharAt(sql.length()-1, ' ');
		sql.append("where ");
		sql.append(prikey.getName()+"="+ReflectUtils.invokeGet(prikey.getName(), obj));
		executeDML(sql.toString(), params.toArray());
		
		return 0;
	}
	
	/**
	 * 查询返回多行数据,并将每行数据封装到clazz对象中
	 * @param sql sql语句
	 * @param clazz 封装数据的javabean类的Class对象
	 * @param args sql参数
	 * @return 查询到的结果
	 */
	public List queryRows(String sql,Class clazz,Object[] args) {
		
		return (List)queryTemplate(sql, clazz, args, new CallBack() {
			
			@Override
			public Object execute(Connection conn, PreparedStatement ps, ResultSet rs) {
				List list = null;
				//ResultSetMetaData接口:可用于获取有关ResultSet对象中列的类型和属性的信息的对象。
				ResultSetMetaData metaData;
				try {
					metaData = rs.getMetaData();
					//多行
					while(rs.next()) {
						if(list==null) {
							list = new ArrayList<>();
						}
						//调用clazz对象的无参构造器生成Object对象
						Object obj = clazz.newInstance();
						//多列
						for(int i=0;i<metaData.getColumnCount()/*获取当前行的列数*/;i++) {
							//获取列名称
							String columnName = metaData.getColumnLabel(i+1);//这里获取列名最好是用getColumnLabel,因为在查询的时候会用到别名
							//获取列对应的值,针对于当前的行
							Object columnValue = rs.getObject(i+1);
							//调用reflectutils工具类的invokeset方法,对bean对象进行传参
							ReflectUtils.invokeSet(columnName, obj, columnValue);
						}
						list.add(obj);
					}
				} catch (Exception e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
				return list;
			}
		});
	}	



	
	/**
	 * 查询返回一行数据,并将每行数据封装到clazz对象中
	 * @param sql sql语句
	 * @param clazz 封装数据的javabean类的Class对象
	 * @param args sql参数
	 * @return 查询到的结果
	 */
	public Object queryOneRow(String sql,Class clazz,Object[] args) {
		//查询结果就只有一列,也就是一个对象
		List list = queryRows(sql, clazz, args);
		return list==null?null:list.get(0);
	}
	
	/**
	 * 查询返回一个值,并返回值
	 * @param sql
	 * @param args
	 * @return 查询到的结果
	 */
	public Object queryValue(String sql,Object[] args) {
		/**
		 * 返回一个值,比如:select count(*) form emp where salary>?;
		 */
		return queryTemplate(sql, null, args, new CallBack() {
			
			@Override
			public Object execute(Connection conn, PreparedStatement ps, ResultSet rs) {
				// TODO Auto-generated method stub
				Object value= null;
				try {
					while(rs.next()) {
						value = rs.getObject(1);
					}
				} catch (SQLException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
				return value;
			}
		});	
			//多行
	}
	
	/**
	 * 查询返回一个数字,并返回数字
	 * Number:抽象类Number是表示数字值可转换为基本数据类型平台类的超类byte , double , float , int , long和short 。
	 * 比如:查询年龄大于30岁的员工有多少人,那么返回的就是一个数字
	 * @param sql
	 * @param args
	 * @return 查询到的数字
	 */
	public Number queryNumber(String sql,Object[] args) {
		//返回一个Number类型的数
		return (Number)queryValue(sql, args);
	}
	
	/**
	 * 分页查询
	 * @param pageNumber 查询结果的第几页
	 * @param pageData  每页显示的行数
	 * @return
	 */
	public abstract Object queryOfPage(int pageNumber,int pageData) ;
	
	/**
	 * 重写clone(),后期通过单例/克隆模式/工厂模式来提高程序的性能
	 */
	@Override
	protected Object clone() throws CloneNotSupportedException {
		// TODO Auto-generated method stub
		return super.clone();
	}
}

<13>Query的子类,MysqlQuery
MysqlQuery

package com.sorm.core;

public class MysqlQuery extends Query {
	
	@Override
	public Object queryOfPage(int pageNumber, int pageData) {
		// TODO Auto-generated method stub
		return null;
	}
}

<14>获取Query对象的工厂:QueryFactory
QueryFactory

package com.sorm.core;
/**
 * 负责根据配置信息创建Query对象
 * @author 
 *
 */
public class QueryFactory {
	
	private static Query query;//原型
	
	static {
		try {
			Class c = Class.forName(DBManager.getCon().getMysqlQueryClass());
			query = (Query) c.newInstance();
		} catch (Exception e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		
	}
	private QueryFactory() {};
	
	public static Query getQueryInstance() {
		try {
			return (Query) query.clone();
		} catch (CloneNotSupportedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
			return null;
		}
	}
}

<15>在上面的过程中,整个小框架的代码已经完成,接下来可以将整个项目的源码打包成jar包了,也可以生成对应的api文档

右键src—export–java–jarFile–next–finish就可以生成jar包了(可以自定义生成路径)
右键src—export–java–javadoc–next–finish就可以生成API文档(可以自定义生成路径)

好了 感觉写的很乱,很尴尬,篇幅太大,感觉写蒙圈了哈哈,大家有啥喷的话尽管提出吧,我接受,只想赶快结束这场战斗…

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值