代码来源于高淇JAVA教学视频 谢谢高淇老师的教学。
因为自己在学习的过程中发现了很多困难点,总结下希望对自己接下来学框架提升。给像我一样得初学者方便。
SORM框架是一个简单的ORM,关系对象映射,可以通过这个框架方便的更改和操作一些数据库的东西,在框架运行的时候也会根据数据库中的表生成相应的Javabean在po包下。通过直接对po包下的操作在运用query中的一些操作就可以实现对数据库的操作。思路会在代码中注释。
QueryFactory直接生产出Query给用的人调用,Query作为一个接口是更好的让多种数据库提供各自的操作。实现mySQLQuery或者OracleQuery,conventor是个转换不同语言数据类型的工具
TableContext就是核心的程序连接数据库表的类。DBManager钟放的是一些配置资源文件和一些加载处理。
明确主要思路,就是通过factory制作一个query,然后直接set方法然后将对象传入query的方法实现功能。查询的话也是通过queryrows方法传入select语句进行查询,因为查询语句的多样性简单的框架没有进一步封装。
先从最底层的tablecontext和DBManage说起:
解释会在代码中标识
public class DBManager {
private static Configuration conf; //这是在bean包下定义的一个类用来把从配置文件提取出来方便用get set
private static List<Connection> pool=new ArrayList<>(); //这是连接池的定义,我在老师的基础上做了些更改,我把连接池全部放到了这个类里
static{ //静态代码块初始化资源文件中的数据, 注意静态块是和类一起加载的,大量用静态影响内存
Properties pro=new Properties(); //这个类是用来从资源文件中提取数据的类
try {
pro.load(Thread.currentThread().getContextClassLoader().getResourceAsStream("database.properties"));
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
conf=new Configuration();
conf.setDriver(pro.getProperty("driver"));
conf.setPoPackage(pro.getProperty("poPackage"));
conf.setPwd(pro.getProperty("pwd"));
conf.setScrPath(pro.getProperty("srcPath"));
conf.setUser(pro.getProperty("user"));
conf.setUrl(pro.getProperty("url"));
conf.setUsingDB(pro.getProperty("usingDB"));
conf.setQueryClass(pro.getProperty("queryClass"));
conf.setMAX_POOL(Integer.parseInt(pro.getProperty("max_pool")));
conf.setMIN_POOL(Integer.parseInt(pro.getProperty("min_pool")));
System.out.println(TableContext.class);//加载类 //这是用来加载表信息的,用于表连接也可以用反射来加载,但是要trycatch选择输出加载
initPool(); //加载连接池
}
public static void initPool(){ //连接池的初始化
if(pool==null){
pool=new ArrayList<Connection>();
}
while(pool.size()<conf.getMIN_POOL()){
pool.add(DBManager.createConn());
//初始化
}
}
public static Connection getConn(){ //连接池取连接
int last_index=pool.size()-1;
Connection conn=pool.get(last_index);
pool.remove(last_index);
return conn;
}
public static void CloseConn(Connection conn){ //连接池关闭
if(pool.size()>conf.getMAX_POOL()){
try {
conn.close();
} catch (SQLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}else {
pool.add(conn);
}
}
public static Connection createConn(){
try { //真正的建立一个连接
Class.forName(conf.getDriver());
return DriverManager.getConnection(conf.getUrl(),conf.getUser(),conf.getPwd());
//直接建立连接
} catch (Exception e) {
// TODO: handle exception
e.printStackTrace();
return null;
}
}
//get用于配置信息的哪个类,很好理解
public static Configuration getConf(){
return conf;
}
//之后的关闭就不说了,主要是要记得把关闭改变成从线程池中假关闭
public static void close(ResultSet rs,Statement ps,Connection conn){
try {
if(rs!=null){
rs.close();
}
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
try {
if(ps!=null){
ps.close();
}
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
try {
if(conn!=null){
CloseConn(conn);
}
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
public static void close(Statement ps,Connection conn){
try {
if(ps!=null){
ps.close();
}
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
try {
if(conn!=null){
CloseConn(conn);
}
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
然后现在有配置文件和连接了
接下来是表连接tablecontect
/**
*
* 管理数据库所有表结构和类结构的关系,并可以根据表结构生成类结构
* @author NodeRed
*
*/
public class TableContext { //最核心的 可以根据表生成类。
/**
* 表名为key,信息对对象为value
*/
public static Map<String,TableInfo> tables=new HashMap<>(); //这个表中的tableInfo也是定义的一个类大概意思就是能够记录下整张表,这里这个map就能记录整个database
/**
* 将po的class对象和表关联
*/
public static Map<Class, TableInfo> poClassTableMap=new HashMap<>(); //这个表用来记录这个表后来会产生哪个类,这后面会说
private TableContext(){};
//无参构造很重要
static{
try {
Connection con=DBManager.getConn(); //这里就获得了和数据库的连接了
DatabaseMetaData dbmd=con.getMetaData(); //这里原视频中没有说,大概是 可以从连接中搜索获取一些元数据,%是类似所有
ResultSet tableRet=dbmd.getTables(null, "%", "%",new String[]{"TABLE"}); //TABLE那就是要查询的就是表了,但是这里出现了问题,意外的搜索出了创建表的时间
System.out.println(tableRet); //记录的那个表,捯饬了半天也没解决。 然后之后是放到了一个结果集里
while(tableRet.next()){ //遍历查找出来的结果
String tableName=(String)tableRet.getObject("TABLE_NAME"); //获得表名
TableInfo ti=new TableInfo(tableName, new ArrayList<ColumnInFo>(), // 然后把表名先把上说的可以记录下整个表的new出来,具体的后面再说
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.setOnlyPrivate(ti.getPriKeys().get(0));
}
} //如果这里没有懂得话,不要急,后面结合javabean
//这里就可以理解为,你在java中建立了一个表格,不是完全填好数据得表格,只是单纯得有每个数据类型在哪个表是不是主键得表示
有了这个标识以后可以在query中找出数据库要操作得是哪个,再从query中操作数据库。
} catch (Exception e) {
// TODO: handle exception
}
updataJavaPOFile(); //因为我们要再数据库中操作,程序事先是不知道你表中得每一项叫什么名字就没办法根据数据可定义好类,在用类来操作数据库
loadPOTables(); //这里我们就可以根据数据库中获取的表框架,获取表的名字,表中字段类型,生成这个数据库的java类
//然后是每次加载这个类的时候更新
}
//一下是实现方法
public static void updataJavaPOFile(){
Map<String,TableInfo> map= TableContext.tables; 这里就通过这个tables表然后用后面的一个java类实现了在项目中构造java类
// TableInfo t=map.get("new_table");
for(TableInfo t:map.values()){
JavaFileUtils.createJavaPOFile(t, new MysqlTypeConventor());
}
}
public static void loadPOTables(){
// Class c=Class.forName("com.cy.sorm.New_table");
// poClassTableMap.put(c, TableInfo); //这里就是用反射把这个类和产生自哪个表放在了一个表里,在后面的操作有用
for(TableInfo tableInfo:tables.values()){
try{
Class c=Class.forName(DBManager.getConf().getPoPackage()+
"."+StringUTils.firstChar2UpCase(tableInfo.getTname()));
poClassTableMap.put(c, tableInfo);
}catch(Exception e){
e.printStackTrace();
}
}
}
// public static void main(String[] args) {
// Map<String, TableInfo> tables=TableContext.tables;
// System.out.println(tables);
// }
}
这里也是orm的核心了,从数据库中复制了一个映射表过来。但是老师说在工作中用的很少。接下来说一下字段映射和表映射吧,可以从以上的javabean包中可以看出,除了很容易理解的配置configuration,javasetget也就是在上面说的拼接我们自己产生的java类的简单结构。
private String name;
private String Datatype;
private int keyType;
字段就是在mysql建表时候的每一列
private String tname;
/**
* key 字段名 value字段类
*/
private Map<String,ColumnInFo> columns;
private ColumnInFo onlyPrivate;
private List<ColumnInFo> priKeys;
这个表呢也就可以记录下整个表了 map中也就是整个表,记录下主键 很好理解这样我们只要有足够多的数据传入我们就可以在java中改了整张表,但是我们必去得做一个query类,用来我把这个数据类传入就可以在数据库中同步更改。
然后大概思路也就成型了,用户可以提供数据库 ,我自动给他生成他表中得类,他调用query中方法,对数据库进行更改。但是怎么通过我生成类对这个映射相联系,然后再映射到数据库呢,我们藏了一个map。
先说一点简单得把tables中获得得数据进行生成java类
这里实现了一个接口,可以处理不同数据库到java得翻译数据类型这里很好懂
public class MysqlTypeConventor implements TypeConvertor{
@Override
public String databaseType2JavaType(String columnType) {
if("varchar".equalsIgnoreCase(columnType)||"char".equalsIgnoreCase(columnType)){
return "String";
}else if("int".equalsIgnoreCase(columnType)
||"tinyint".equalsIgnoreCase(columnType)){
return "Integer";
}else if("double".equalsIgnoreCase(columnType)){
return "double";
}else if("timestamp".equalsIgnoreCase(columnType)){
return "Timestamp";
}
return null;
}
@Override
public String javaType2databaseType(String javaDataType) {
// TODO Auto-generated method stub
return null;
}
}
然后我们把转换器和传进去的字段封装成一个java生成类的工具,就是上面代码提到的哪个工具类
public class JavaFileUtils {
public static JavaFieldGetSet createFieldGetSetSRC(ColumnInFo column,TypeConvertor convertor){
JavaFieldGetSet jfgs= new JavaFieldGetSet();
String javaFieldType = convertor.databaseType2JavaType(column.getDatatype());
jfgs.setFieldInfo("\tprivate "+javaFieldType+" "+column.getName());
StringBuilder getSrc=new StringBuilder();
getSrc.append("\tpublic "+javaFieldType+" get"+StringUTils.firstChar2UpCase(column.getName())+"(){\n");
getSrc.append("\t\treturn "+column.getName()+";\n");
getSrc.append("\t}\n");
jfgs.setGetInfo(getSrc.toString());
StringBuilder setSrc=new StringBuilder();
setSrc.append("\tpublic void set"+StringUTils.firstChar2UpCase(column.getName())+"(");
setSrc.append(javaFieldType+" "+column.getName()+"){\n");
setSrc.append("\t\tthis."+column.getName()+"="+column.getName()+";\n");
setSrc.append("\t}\n");
jfgs.setSetInfo(setSrc.toString());
return jfgs;
}
public static String createJavaSrc(TableInfo tableInfo,TypeConvertor convertor){
Map<String,ColumnInFo> columns=tableInfo.getColumns();//表装入map
List<JavaFieldGetSet> javaFields=new ArrayList<>();
for(ColumnInFo c:columns.values()){
javaFields.add(createFieldGetSetSRC(c, convertor));
}
StringBuilder src=new StringBuilder();
src.append("package "+DBManager.getConf().getPoPackage()+";\n\n");
src.append("import java.util.*;\n\n");
src.append("import java.sql.*;\n\n");
src.append("public class "+StringUTils.firstChar2UpCase(tableInfo.getTname())+"{\n\n");
for(JavaFieldGetSet f:javaFields){
src.append(f.getFieldInfo()+";\n");
}
src.append("\n\n");
for(JavaFieldGetSet f:javaFields){
src.append(f.getSetInfo());
}
src.append("\n");
for(JavaFieldGetSet f:javaFields){
src.append(f.getGetInfo());
}
src.append("}\n");
// System.out.println(src);
return src.toString();
}
public static void createJavaPOFile(TableInfo tableInfo,TypeConvertor convertor){
String src=createJavaSrc(tableInfo, convertor);
BufferedWriter bw=null;
String srcPath=DBManager.getConf().getScrPath()+"\\";
String packagePath=DBManager.getConf().getPoPackage().replaceAll("\\.", "\\\\");
File f=new File(srcPath+packagePath.trim());
System.out.println(f);
if(!f.exists()){
f.mkdirs();
}
try {
bw=new BufferedWriter(new FileWriter(f.getAbsoluteFile()+"\\"+StringUTils.firstChar2UpCase(tableInfo.getTname())+".java"));
bw.write(src);
} catch (Exception e) {
// TODO: handle exception
}finally {
if(bw!=null){
try {
bw.close();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
public static void main(String[] args) {
// ColumnInFo ci=new ColumnInFo("username","varchar",0);
// JavaFieldGetSet f=createFieldGetSetSRC(ci, new MysqlTypeConventor());
// System.out.println(f);
Map<String,TableInfo> map= TableContext.tables;
// TableInfo t=map.get("new_table");
for(TableInfo t:map.values()){
JavaFileUtils.createJavaPOFile(t, new MysqlTypeConventor());
}
}
}
第一个方法就是把从字段类中获取的名字类型转换成一个String,这里没有表名所以拼接了的是getset,便于拼接是用stringbuilder拼接,
第二个方法有了表映射以后我们就可以拼接表名,然后从表映射中获取有那些字段,再拼接上字段的setget方法,最后呢就是通过配置文件中获得我们项目路径,然后建立java类
这里注意把“ . ”换成" \\ "的操作java中一个变两个
然后就是核心的实现query了,这里本来是想实现接口 然后方便不同的数据库连接不同的,然后后来因为方法重用的很多就改成了抽象类。
public abstract class Query {
public List executeQueryTemplate(String sql, Object[] params,Class clazz,CallBack back){
Connection conn=DBManager.getConn();
PreparedStatement ps=null;
ResultSet rs=null;
try {
ps=conn.prepareStatement(sql);
JDBCUtils.handleParams(ps, params); //这个是一个回调模板,通过callback,callback是一个简单的接口,很常用,实现的作用就是,我实现一个接口,接口中是我要实现的功能
rs=ps.executeQuery(); //然后我可以接着写下去,这成为了一个模板,只是中间这个方法的实现不一样,当我要使用这个模板的时候调用这个方法,然后匿名内部类实现back
return back.doExecute(conn, ps, rs); //中的方法但是匿名内部类又涉及到用外部变量 final的问题,我用过的也很少希望在之后的学习有更深的理解。
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
return null;
}finally {
DBManager.close(rs,ps,conn);
}
}
public int excuteDML(String sql,Object[] params){
Connection conn=DBManager.getConn();
int count=0; //这是个执行sql语句的封装,object参数是传入要操作的数因为用的是preparestatement
PreparedStatement ps=null;
try {
ps=conn.prepareStatement(sql);
JDBCUtils.handleParams(ps, params);
count=ps.executeUpdate();
} catch (SQLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}finally{
DBManager.close(ps,conn);
}
return count;
}
public void insert(Object obj){
Class c=obj.getClass();
List<Object> params=new ArrayList<>();//储存sql的参数对象
int countNotNull=0;//计算不为空的属性值
TableInfo tableInfo = TableContext.poClassTableMap.get(c);//////////////////这就是map记录了class和映射table的关系,然后返回映射表就可以获得表名拼接sql语句了
StringBuilder sql=new StringBuilder("insert into "+tableInfo.getTname()+" (");
Field[] fs=c.getDeclaredFields(); //利用反射将对象的的名字和操作值然后拼接字符串
for(Field f:fs){
String fieldName=f.getName();
Object fieldValue=ReflectUtils.invokeGet(fieldName, obj); //这里是利用反射 获取get方法 get到我们设置的值 然后拼接到sql语句中,
//这样一来最开始的思路也解决了
if(fieldValue!=null){
countNotNull++;
sql.append(fieldName+",");
params.add(fieldValue);
}
}
sql.setCharAt(sql.length()-1, ')');
sql.append(" value (");
for(int i=0;i<countNotNull;i++){
sql.append("?,");
}
sql.setCharAt(sql.length()-1, ')');
excuteDML(sql.toString(), params.toArray());
}
public void delete(Class clazz,Object id){
/**
* table.class->delete from New_table where id=2;
*/
TableInfo tableInfo=TableContext.poClassTableMap.get(clazz);
ColumnInFo onlyPriKey=tableInfo.getOnlyPrivate(); //mysql删除宇语句中我们只要获取表主键的编号就可以删除了
String sql="delete from "+tableInfo.getTname()+" where "+onlyPriKey.getName()+"=? ";
excuteDML(sql, new Object[]{id});
}
public void delete(Object obj){
Class c=obj.getClass();
TableInfo tableInfo=TableContext.poClassTableMap.get(c); //同理反射获得get方法 然后用第一个delete方法删除
ColumnInFo onlyPriKey= tableInfo.getOnlyPrivate();
Object priKeyValue=ReflectUtils.invokeGet(onlyPriKey.getName(), obj);
delete(c,priKeyValue);
}
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<>();
TableInfo tableInfo=TableContext.poClassTableMap.get(c);
ColumnInFo priKey =tableInfo.getOnlyPrivate();
StringBuilder sql=new StringBuilder("update "+tableInfo.getTname()+" set ");
for(String fname:fieldNames){
Object fvalue=ReflectUtils.invokeGet(fname, obj);
params.add(fvalue);
sql.append(fname+"=?,");
//和insert很像加了遍历要更新的列 然后list记录问好处要更新的值 反射获取值
}
sql.setCharAt(sql.length()-1, ' ');
sql.append(" where ");
sql.append(priKey.getName()+"=? ");
params.add(ReflectUtils.invokeGet(priKey.getName(), obj));
return excuteDML(sql.toString(), params.toArray());
}
//返回多行
public List queryRows(String sql, Class clazz, Object[] params){
return executeQueryTemplate(sql,params,clazz,new CallBack(){ ///接上回调方法的实现
public List 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();//调用javabeen的无参构造器
// select username,pwd,age from user where id>? and age<18
for(int i=0;i<metaData.getColumnCount();i++){ //getcolumnCount为查询要差的列如 username pwd age
String columnName=metaData.getColumnLabel(i+1);
Object columnValue=rs.getObject(i+1);
ReflectUtils.invokeSet(rowObj, columnName, columnValue);
}
list.add(rowObj);
}
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
return null;
}
return list;
}
});
}
//返回一行
public Object queryUniqueRows(String sql, Class clazz,Object[] params){
List list=queryRows(sql, clazz, params);
return (list==null&&list.size()>0)? null:list.get(0);
}
public Object queryValue(String sql,Object[] params){
Connection conn=DBManager.getConn();
Object value=null;
PreparedStatement ps=null;
ResultSet rs=null;
try {
ps=conn.prepareStatement(sql);
JDBCUtils.handleParams(ps, params);
rs=ps.executeQuery();
// rs中为查到的行数
while(rs.next()){
value=rs.getObject(1); //rs结果集中不是以0开头的
}
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return value;
}
public Number queryNumber(String sql,Object[] params){
return (Number)queryValue(sql, params);
}
public abstract Object queryPagenate(int PageNum,int size); //抽象方法,不同子类不同实现
}
之后就可以继承query实现不同的数据库的不同方法然后重写和修改实现多态
然后实现了query后就可以工厂设计模式,用factory单例,然后生产query
public class QueryFactory {
private static QueryFactory factory=new QueryFactory();
private static Class c;
static{
try {
c=Class.forName(DBManager.getConf().getQueryClass());
} catch (ClassNotFoundException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
private QueryFactory() {
// TODO Auto-generated constructor stub
}
public Query creatFactory(){
try {
return (Query) c.newInstance();
} catch (InstantiationException e) {
// TODO Auto-generated catch block
e.printStackTrace();
return null;
} catch (IllegalAccessException e) {
// TODO Auto-generated catch block
e.printStackTrace();
return null;
}
}
}
原文出处:https://www.cnblogs.com/doyourwant/p/11792237.html
来源:oschina
链接:https://my.oschina.net/u/4303671/blog/3248259