java web开发 数据访问层

引言

在刚开始学习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模式,数据库访问与业务逻辑的分离,因此我也认为一个好的项目,必需要现有一个好的架构。

猜你喜欢

转载自blog.csdn.net/qq_36425506/article/details/80525550