MVC - pojo和底层DAO

Pojo编写

pojo即值对象,用于单纯地存储某个值或某些值所构建的对象,以存储用户信息为例,则编写一个`UserDetial`类,在其中设定好需要存储的每个用户信息,并为此类添加有参、无参构造方法、getter和setter,为了以后调试便利,顺便重写`toString()`方法:

public class UserDetail {
    private Integer id;
    private String name;
    private Integer age;
    private Integer height;

    public UserDetail() {
    }

    public Integer getId() {
        return id;
    }

    public void setId(Integer id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public Integer getAge() {
        return age;
    }

    public void setAge(Integer age) {
        this.age = age;
    }

    public Integer getHeight() {
        return height;
    }

    public void setHeight(Integer height) {
        this.height = height;
    }

    @Override
    public String toString() {
        return "UserDetail{" +
                "id=" + id +
                ", name='" + name + '\'' +
                ", age=" + age +
                ", height=" + height +
                '}';
    }
}

工具类编写

后期为了保证事务的原子性,将引入OpenSessionInViewFilter,所以需要将数据库的连接对象置于一个公共的、都可访问的共享环境中,方便起见,编写两个工具类:

  1. ConnUtil工具类,负责从ThreadLocal获取conn或直接生成conn并将之放入ThreadLocal,对象返回给2类使用;另外提供一个conn的关闭方法,避免2类直接操作conn对象,保证封装
    public class ConnUtil extends ThreadLocal{
        //设定数据库连接参数
        private static String name = "root";
        private static String pwd = "******";
        private static String url = "jdbc:mysql://localhost:3306/demo";
        private static ThreadLocal<Connection> threadLocal = new ThreadLocal<>();  //负责传递connection的ThreadLocal
    
        private static Connection createConn() {   //仅负责新建连接
            try {
                Class.forName("com.mysql.jdbc.Driver");
                return DriverManager.getConnection(url, name, pwd);
            } catch (SQLException | ClassNotFoundException e) {
                e.printStackTrace();
            }
            return null;
        }
    
        public static Connection getConn() {   //在获取前判断一下是否存在,若不存在则先调用新建连接方法
            Connection conn = threadLocal.get();
            if(conn == null) {
                threadLocal.set(createConn());
                conn = threadLocal.get();
            }
            return conn;
        }
    
        public static void close() {   //关闭connection,并去除ThreadLocal
            Connection conn = threadLocal.get();
            if(conn == null) {
                return;
            }
            try {
                if(!conn.isClosed()) {
                    conn.close();
                    threadLocal.remove();
                }
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
    }
  2. TransactionManager类,提供“开始事务”、“提交事务”、“回滚事务”三个方法,调用1类,直接使用其返回值

  当前主要任务是编写DAO,这个类将在之后划分事务时编写

BaseDAO编写

BaseDAO这个类的直接目的就是提供一个以方法为基础的SQL语句执行通道,其他DAO直接调用此类,就可以便捷地实现对数据库基本的增删改查

对这个类的相关信息进行规划:

  • 为了保证此类的泛用性,考虑将之设定为泛型类;
  • 前面已经编写工具类ConnUtil,因此与数据库的连接直接从ThreadLocal中取;(这一步实际上完成了部分事务管理的工作)
  • 执行SQL命令考虑使用PreparedStatement,以便进行搜索条件的装填和替换;
  • 最后,为了在返回对象时,能够通过反射获取正确的对象和属性信息,有必要在构造函数中先获取自己当前的泛型名称

BaseDAO代码:

public class BaseDAO<T> {
    private Connection connection = null;
    private Class superClass;
    private PreparedStatement ps;

    public BaseDAO() {
        //genericType其实是BaseDAO的Class
        Type genericType = getClass().getGenericSuperclass();
        Type[] actualTypeArguments = ((ParameterizedType) genericType).getActualTypeArguments();
        Type actualType = actualTypeArguments[0];
        try {
            superClass = Class.forName(actualType.getTypeName());
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }


    }

    private boolean isDefaultType(String typeName) {   //分辨是否是自己编写的类型
        return "java.lang.String".equals(typeName)
                || "java.lang.Integer".equals(typeName)
                || "java.util.Date".equals(typeName)
                || "java.sql.Date".equals(typeName);
    }

    private boolean isNotDefaultType(String typeName) {
        return !isDefaultType(typeName);
    }
    
	//装填PreparedStatement的参数,以替换“?”
    private void setParams(PreparedStatement ps, Object... params) throws SQLException {
        if (params != null && params.length > 0) {
            for (int i = 0; i < params.length; i++)
                ps.setObject(i + 1, params[i]);
        }
    }
    
	//根据传来的数据装填该对象的属性信息
    private void setValues(Object Obj, String name, Object value) {
        Field field = null;  //通过提供的属性名,拿到传入的目标对象的内部属性
        try {
            field = Obj.getClass().getDeclaredField(name);
            String typeName = field.getType().getName();   //获取该署名类型名称,看是不是自己写的类
            if (isNotDefaultType(typeName)) {  //是自己写的类
                //创建一个此类实例
                value = Class.forName(typeName).getDeclaredConstructor().newInstance();
            }
            //将对象赋值给属性
            field.setAccessible(true);
            field.set(Obj,value);
        } catch (NoSuchFieldException | ClassNotFoundException | InvocationTargetException | InstantiationException |
                 IllegalAccessException | NoSuchMethodException e) {
            throw new RuntimeException(e);
        }
    }

    public int update(String query, Object... params) throws SQLException {
        connection = ConnUtil.getConn();
        ps = connection.prepareStatement(query);
        setParams(ps, params);
        return ps.executeUpdate();
    }

    public T getOneObj(String query, Object... params) {
        connection = ConnUtil.getConn();
        ResultSet resultSet;
        try {
            ps = connection.prepareStatement(query);
            setParams(ps, params);
            resultSet = ps.executeQuery();   //获取结果

            ResultSetMetaData resultSetMetaData = resultSet.getMetaData();  //得到MetaData,用于得到列数和获取列名
            int len = resultSetMetaData.getColumnCount();

            if (resultSet.next()) {
                T Obj = (T) superClass.getConstructor().newInstance();   //实例化一个T
                for(int i=0;i<len;i++) {
                    //往T里塞值
                    setValues(Obj,resultSetMetaData.getColumnName(i+1),resultSet.getObject(i+1));
                }
                return Obj;
            }
        } catch (SQLException | NoSuchMethodException | IllegalAccessException | InvocationTargetException |
                 InstantiationException | ClassNotFoundException | NoSuchFieldException e) {
            e.printStackTrace();
        }
        return null;
    }

	//得到的多个结果封装为对象列表
	 public List<T> getSeriesObj(String query, Object... params) {
        connection = ConnUtil.getConn();
        ResultSet resultSet;
        List<T> res = new ArrayList<T>();
        try {
            ps = connection.prepareStatement(query);
            setParams(ps,params);
            resultSet = ps.executeQuery();

            ResultSetMetaData resultSetMetaData = resultSet.getMetaData();  //得到MetaData,用于得到列数和获取列名
            int len = resultSetMetaData.getColumnCount();

            while (resultSet.next()) {
                T Obj = (T) superClass.getConstructor().newInstance();   //实例化一个T
                for(int i=0;i<len;i++) {
                    //往T里塞值
                    setValues(Obj,resultSetMetaData.getColumnName(i+1),resultSet.getObject(i+1));
                }
                res.add(Obj);
            }
        } catch (SQLException | NoSuchMethodException | IllegalAccessException | InstantiationException |
                 InvocationTargetException | ClassNotFoundException | NoSuchFieldException e) {
            e.printStackTrace();
        }
        return res;
    }
}

DAO接口和实现类编写

完成了BaseDAO的编写后,就要进行DAO接口及其实现类的编写,注意需要保证DAO接口方法都是细粒度方法,即一个方法仅完成一个面向实际对象的操作,例如根据id删除某用户的操作

DAO接口代码:

public interface UserDetailDAO {
    void addUser(UserDetail userDetail);  //增加用户
    UserDetail getUser(int id);  //根据id获取用户对象
    void delUser(int id);  //根据id删除用户对象
    List<UserDetail> getSeriesUser(String query, Object... params);  //根据提供的条件获取多个用户
}

相应的,需要编写此接口的实现类,并通过BaseDAO的调用来实现接口中的方法:

public class UserDetailDAOImpl extends BaseDAO<UserDetail> implements UserDetailDAO {

    @Override
    public void addUser(UserDetail userDetail) {
        try {
            int res = super.update("insert into userbasic values(?,?,?,?)", userDetail.getId(), userDetail.getName(), userDetail.getAge(), userDetail.getHeight());
            System.out.println("添加完成," + res + "行受影响");
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }

    @Override
    public UserDetail getUser(int id) {
        return super.getOneObj("select * from userbasic where id=?", id);
    }

    @Override
    public void delUser(int id) {
        try {
            int res = super.update("delete from userbasic where id=?", id);
            System.out.println("删除完成," + res + "行受影响");
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }
	@Override
    public List<UserDetail> getSeriesUser(String query, Object... params) {
        return super.getSeriesObj(query,params);
    }
}

测试

编写测试类进行测试:

public class UserDetailDAOTest {
    public static void main(String[] args) throws SQLException {
        UserDetailDAOImpl userDAO = new UserDetailDAOImpl();
        userDAO.addUser(new User(3,"fyc",20,180));
        User user = userDAO.getUser(3);
        System.out.println(user);
        userDAO.delUser(3);
        List<User> list = userDAO.getSeriesUser("select * from userbasic",null);
        for(UserDetail u: list)
            System.out.println(u);
    }
}

在执行时遇到错误:

在这里插入图片描述
使用e.printStackTrace()后,发现问题出在setValues()方法的Class.forName()找不到对应的类,观察出错点,发现程序已经进入if语句之中,再通过打印出当前的typeName,进一步确定是类型名不能识别的原因

所以,错误原因是,在判断是否是自创类时,判断的依据有误,具体原因是:在原本的UserDetail类中,只有int类型属性,没有Integer类型属性而int不在java.lang中,因此可以直接将pojo的int类型属性改为Integer,使之符合所指定的包装类,能被正确识别即可

最后,成功执行全部代码:

在这里插入图片描述

posted @ 2022-09-05 09:28  FICeN  阅读(57)  评论(0)    收藏  举报