引言
在刚开始学习javaweb开发时,接触到的开发模式是MVC,使用原生的servlet在tomcat中开发web项目,因此我在数据访问上也没有用开源框架,而是自己动手封装了一个数据库访问层,但之后对比流行的写法,犯的错误不少,学习到了很多。
最开始自己想法
最开始我的代码是如下放置的:
* bean (业务处理元对象,只包含get和set方法)
* jdbc (数据库访问层)
* tablemanger (继承MySQLManger 具体到每一个表的Manger)
* MySQLHelper.java
* MySQLManger.java
MySQLHelper.java是封装了数据库链接过程,并能放回链接对象。
/**
* 此类是jdbc驱动链接数据库类,封装了链接数据库过程,单例模式保证应用程序与数据库链接唯一
*/
public class MySQLHelper {
private static final String JDBC_DRIVER = "com.mysql.jdbc.Driver";
private static final String DB_URL = "jdbc:mysql://localhost/taobao?useUnicode=true&characterEncoding=UTF8";
private static final String username = "xxxx";
private static final String password = "xxxxxx";
private Connection connection = null;
private static MySQLHelper instance;
public static Log log = LogFactory.getLog("SimpleLog");
private MySQLHelper(){
try {
Class.forName(JDBC_DRIVER);
log.info("注册jdbc驱动...");
connection = DriverManager.getConnection(DB_URL,username,password);
log.info("数据库链接成功!!!");
}catch (ClassNotFoundException e){
log.error("jdbc驱动注册失败!!!");
log.error(e.toString());
}catch (SQLException e){
log.error("数据库链接失败!!!");
log .error(e.toString());
}
}
public static synchronized MySQLHelper getInstance(){
if(instance !=null){
return instance;
}else{
instance = new MySQLHelper();
return instance;
}
}
public Connection getConnection() {
return connection;
}
public void setConnection(Connection connection) {
this.connection = connection;
}
}
MySQLManger封装了每一个manger获取数据库链接和关闭数据库链接方法。
public abstract class MySQLManger {
private MySQLHelper helper = null;
protected Connection connection = null;
public static Log log = LogFactory.getLog("SimpleLog");
public MySQLManger(){
helper = MySQLHelper.getInstance();
connection = helper.getConnection();
}
/**
* 关闭数据库链接,每次操作完成后必须调用close(),浪费资源
*/
public void close(){
Connection connection = helper.getConnection();
try {
if(connection!= null){
connection.close();
}
}catch (SQLException e){
log.error(e.toString());
}
}
}
我在数据访问的大致思路就是封装数据获取连接的操作,每个表都有一个不同的manger,使用不同的manger对不同的表进行数据访问存取。
下面是对user表的操作的Usermanger:
/**
* 此类是操作user表的类
*/
public class UserManger extends MySQLManger{
private Statement statement = null;
public UserManger(){
try {
statement = connection.createStatement();
}catch(SQLException e){
log.error(e.toString());
}
}
/**
* 添加一个用户数据
* @param id_number 帐号
* @param password 密码
* @param phonenumber 电话号码
* @param address 地址
* @param email_address 邮箱地址
*/
public void addUser(String id_number,String password ,String phonenumber, String address ,String email_address){
try {
statement.execute("INSERT INTO users(number,password ,phonenumber,address,email_address) VALUES ("
+"'"+id_number+"'"+","
+"'"+password+"'"+","
+"'"+phonenumber+"'"+","
+"'"+address+"'"+","
+"'"+email_address+"'"+
");");
}catch(SQLException e){
log.error(e.toString());
}
}
/**
* 删除一个用户数据
* @param id_number 账户
*/
public boolean deleteUser(String id_number){
try {
statement.execute("DELETE FROM users WHERE " +
"number ="+"'"+ id_number+"';");
}catch(SQLException e){
log.error(e.toString());
return false;
}
return true;
}
/**
* 更新用户密码
* @param newpassword 新密码
*/
public boolean updatePassword(String id_number,String newpassword){
try{
statement.execute("UPDATE users SET password ="+ "'"+newpassword+"'"+
"WHERE number = "+ "'"+id_number+"';");
}catch(SQLException e){
log.error(e.toString());
return false;
}
return true;
}
/**
* 更新用户电话号码
* @param newphonenumber 新电话号码
*/
public boolean updatePhoneNumber(String id_number,String newphonenumber){
try{
statement.execute("UPDATE users SET phonenumber =" + "'"+newphonenumber+"'"+
"WHERE number = "+ "'"+ id_number+"';");
}catch(SQLException e){
log.error(e.toString());
return false;
}
return true;
}
/**
* 更新用户地址
* @param newaddress 用户地址
* @return false失败 true 成功
*/
public boolean updateAddress(String id_number,String newaddress){
try{
statement.execute("UPDATE users SET address ="+"'"+newaddress+"'"+
"WHERE number = "+ "'"+ id_number+"';");
}catch(SQLException e){
log.error(e.toString());
return false;
}
return true;
}
/**
* 更新用户电子邮箱
* @param newEmailAddress 用户电子邮箱
* @return false 失败 true 成功
*/
public boolean updateEmailAddress(String id_number,String newEmailAddress){
try{
statement.execute("UPDATE users SET email_address ="+"'"+newEmailAddress+"'"+
"WHERE number = "+ "'"+id_number+"';");
}catch(SQLException e){
log.error(e.toString());
return false;
}
return true;
}
/**
* 查询账户是否存在
* @param usernumber
* @return
*/
public boolean isExistUM(String usernumber){
ResultSet resultSet = null;
int counts = -1;
try {
resultSet = statement.executeQuery("select count(*) from users where number = "+
"'"+usernumber+"';");
resultSet.next();
counts = resultSet.getInt(1);
}catch(SQLException e){
log.error(e.toString());
}
if (counts >0){
return true;
}else{
return false;
}
}
/**
* 查询电话是否使用过
* @param phonenumber
* @return
*/
public boolean isExistPN(String phonenumber){
ResultSet resultSet = null;
int counts = -1;
try {
resultSet = statement.executeQuery("select count(*) from users where phonenumber = "+
"'"+phonenumber+"';");
resultSet.next();
counts = resultSet.getInt(1);
}catch(SQLException e){
log.error(e.toString());
}
if (counts >0){
return true;
}else{
return false;
}
}
/**
* 获得用户完整信息
* @param id_number
* @return
*/
public User queryUser(String id_number){
ResultSet resultSet = null;
User user = new User();
String id = null;
String pw = null;
String pn = null;
String ad = null;
String em_ad = null;
try{
resultSet = statement.executeQuery("SELECT * FROM users where number="+ "'"+id_number+"';");
resultSet.next();
id = resultSet.getString("number");
pw = resultSet.getString("password");
pn = resultSet.getString("phonenumber");
ad = resultSet.getString("address");
em_ad = resultSet.getString("email_address");
}catch(SQLException e){
log.error(e.toString());
}
if(id!= null&& pw !=null&& pn !=null &&ad!= null && em_ad !=null){
user.setNumber(id);
user.setPassword(pw);
user.setPhonenumber(pn);
user.setAddress(ad);
user.setEmail_address(em_ad);
}
return user;
}
}
首先第一问题是,我写的数据库访问层中根本没有使用到User这个对象,完全是知道表内容在写构造函数。
第二个问题是对数据库访问抽象度不够,用户更改更改邮箱,密码等等都要重新编写一个函数。每多一个需求,我就要添加一个函数,重新编写sql语句,很多重复内容,假如表中数据很多的话开发起来非常复杂。
第三个问题是数据访问层 业务逻辑层含糊不清。可以看到我在数据库访问层上还在写判断用户是否存在,这是非常错误的做法,会使项目变得十分臃肿,后期修改难度也很大。
修改后
修改后代码包放置如下:
* bean
* dao (数据库访问)
* service (业务逻辑服务)
其中在dao包中有一个类是DbHelper,重新抽象了sql语句执行的方法,从返回结果出发,封装了四类当前执行sql语句的需求。
1.执行预处理sql语句
2.执行查询单行数据,获得具体对象(反射)
3.执行查询多行数据,过得对象集合(反射)
4.执行聚合函数预处理语句
public class DBHelper {
static final String DB_URL = "jdbc:mysql://localhost/taobao?useUnicode=true&characterEncoding=UTF8";
static final String USER_NAME = "xxxx";
static final String PASS_WORD = "xxxxx";
//为每个线程保存不同的变量
private static ThreadLocal<Connection> cons = new ThreadLocal<>();
/**
* 获得数据库链接对象
* @return Connection
* @throws DAOException
*/
public static Connection getConnection() throws DAOException{
Connection con = cons.get();
if (con ==null){
try {
Class.forName("com.mysql.jdbc.Driver");
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
try {
con = DriverManager.getConnection(DB_URL,USER_NAME,PASS_WORD);
} catch (SQLException e) {
e.printStackTrace();
throw new DAOException();
}
cons.set(con);
}
return con;
}
/**
* 关闭数据库链接对象
*/
public static void closeConnection(Connection connection) throws DAOException{
try {
if(connection != null&& !connection.isClosed()){
connection.close();
}
} catch (SQLException e) {
e.printStackTrace();
throw new DAOException();
}
}
/**
* 预处理sql语言并执行
* @param sql 待预处理sql语句
* @param params 预处理参数
* @throws DAOException
*/
@Deprecated
public static void executeSQL(String sql,Object... params) throws DAOException{
Connection connection = getConnection();
try {
PreparedStatement preparedStatement = connection.prepareStatement(sql);
for (int i = 0; i<params.length; i++){
//perpareStatement 标志从1开始
preparedStatement.setObject(i+1,params[i]);
}
preparedStatement.execute();
} catch (SQLException e) {
e.printStackTrace();
throw new DAOException();
}
}
/**
* 获得单行数据
* @param sql 预处理语句
* @param c bean
* @param params 预处理参数
* @param <T> bean类
* @return
*/
@SuppressWarnings("deprecation")
@Deprecated
private static <T> List<T> getResults(String sql , Class<T> c, Object... params) {
List<T> result = new ArrayList<>();
Connection con = getConnection();
try {
PreparedStatement preparedStatement = con.prepareStatement(sql);
for (int i = 0;i< params.length;i++){
preparedStatement.setObject(i+1,params[i]);
}
ResultSet resultSet = preparedStatement.executeQuery();
while(resultSet.next()){
T t = c.newInstance();
Field[] fields = c.getDeclaredFields();
for (Field field :fields){
String fieldName = field.getName();
//元数据,数据描述数据
ResultSetMetaData metaData = resultSet.getMetaData();
int columnCount = metaData.getColumnCount();
//是否属性是否存在
boolean isExist =false;
for (int i = 1; i <= columnCount; i++) {
String columnName = metaData.getColumnName(i);
if (columnName.equalsIgnoreCase(fieldName)) {
isExist = true;
break;
}
}
if (!isExist){
continue;
}
String setMethod = "set" +fieldName.substring(0,1).toUpperCase()+fieldName.substring(1);
Method method =c.getDeclaredMethod(setMethod,field.getType());
if (field.getType()==String.class){
method.invoke(t,resultSet.getString(fieldName));
}else if (field.getType()== int.class){
method.invoke(t,resultSet.getInt(fieldName));
}
}
result.add(t);
}
} catch (Exception e) {
e.printStackTrace();
throw new DAOException();
}
return result;
}
/**
* 使用commons_util执行sql语句
* @param sql 数据库访问语句
* @param params 预处理参数
*/
public static void executeSQL_util(String sql, Object... params) {
QueryRunner runner = new QueryRunner();
try {
runner.update(getConnection(), sql, params);
} catch (SQLException e) {
e.printStackTrace();
throw new DAOException();
}
}
/**
* 使用commons_util获得单行
* @param sql 数据库访问语句
* @param c bean
* @param params 预处理参数
* @param <T> bean类
* @return
*/
public static <T> T getSingle_util(String sql, Class<T> c, Object... params) {
QueryRunner runner = new QueryRunner();
try {
return runner.query(getConnection(), sql, new BeanHandler<>(c), params);
} catch (SQLException e) {
e.printStackTrace();
throw new DAOException();
}
}
/**
* 使用commons_util获得多行内容
* @param sql 数据库访问语句
* @param c bean
* @param params 预处理参数
* @param <T> bean类
* @return
*/
public static <T> List<T> getResults_util(String sql, Class<T> c, Object... params) {
QueryRunner runner = new QueryRunner();
try {
return runner.query(getConnection(), sql, new BeanListHandler<>(c), params);
} catch (SQLException e) {
e.printStackTrace();
throw new DAOException();
}
}
/**
* 执行SQL返回单行单列的数据(聚合函数 count,sum,max,min,avg)
* @param sql 预处理语句
* @param c bean
* @param params 参数
* @param <T> bean类
* @return
*/
public static <T> T getScalar_util(String sql, Class<T> c, Object... params) {
QueryRunner runner = new QueryRunner();
try {
return runner.query(getConnection(), sql, new ScalarHandler<T>(), params);
} catch (SQLException e) {
e.printStackTrace();
throw new DAOException();
}
}
}
UserDAO类是一User为最小数据单元进行数据访问:
/**
* 添加一个用户
* @param user
*/
public void addUser(User user){
String sql = "INSERT INTO users(number,password ,phonenumber,address,email_address) VALUES ("
+"'"+user.getNumber()+"'"+","
+"'"+user.getPassword()+"'"+","
+"'"+user.getPhonenumber()+"'"+","
+"'"+user.getAddress()+"'"+","
+"'"+user.getEmail_address()+"'"+
");";
DBHelper.executeSQL_util(sql);
}
可以看到这里只有sql语句的拼接然后在,调用执行函数就行,非常方便。
之后在service包中就包含了与我们业务相关的方法,这里介绍一下通过cglib代理实现事务操作
package cn.skypointer.service;
import java.lang.reflect.Method;
import java.sql.Connection;
import cn.skypointer.dao.DBHelper;
import cn.skypointer.exception.ServiceException;
import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
/**
* 代理事务工厂,数据库引擎需要设置为innodb才支持事务操作
*/
public class ServiceProxyFactory {
private Enhancer enhancer = new Enhancer();
/**
* 返回被代理service,servic中的方法访问不成功将回滚,事务代理
* @param c
* @param <T>
* @return
*/
public static <T> T getService(Class<T> c) {
ServiceProxyFactory f = new ServiceProxyFactory();
//绑定service
f.enhancer.setSuperclass(c);
f.enhancer.setCallback(new MethodInterceptor() {
@Override
public Object intercept(Object o, Method m, Object[] params, MethodProxy proxy) throws Throwable {
//获得数据库链接
Connection conn = DBHelper.getConnection();
//设置为不自动提交,sql命令储存在缓存中
conn.setAutoCommit(false);
Object result = null;
try {
result = proxy.invokeSuper(o, params);// 调用代理目标的方法
conn.commit();
} catch (Exception e) {
e.printStackTrace();
conn.rollback();
throw new ServiceException();
} finally {
DBHelper.closeConnection(conn);
}
return result;
}
});
return (T) f.enhancer.create();
}
}
通过这个代理工厂我们的数据库操作函数就会被代理,调用方法会被拦截,如果出错就回滚,代码中很清晰的表现了出来。
结语
通过对数据库访问层的重写,更加深刻理解了mvc模式,数据库访问与业务逻辑的分离,因此我也认为一个好的项目,必需要现有一个好的架构。