如有错误,请多指教!
在学习了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数据类型转换成数据库类型 |
MysqlTypeConvertor | TypeConvertor接口的实现类,主要负责数据库字段类型的转换,转换为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文档(可以自定义生成路径)
好了 感觉写的很乱,很尴尬,篇幅太大,感觉写蒙圈了哈哈,大家有啥喷的话尽管提出吧,我接受,只想赶快结束这场战斗…