开发者社区> 问答> 正文

怎样设计pojo与Dao,求高手赐教!?报错

各位大牛,早上好,有个问题一直在困扰我,希望能得到解答。

我在开发一个项目,没有使用框架,技术选型使用的是Servlet和JSP,与数据库通信使用的JDBC,就这么简单。

架构使用的三层架构与MVC,页面使用JSP,控制器Servlet,模型是Service,Service持有Dao层的对象,写有一个DBHelper:


public class DBHelper {
	
	
	public static ComboPooledDataSource cpds = null;
	
	static{
		cpds = new ComboPooledDataSource();
		try {
			cpds.setDriverClass("com.mysql.jdbc.Driver");
			cpds.setJdbcUrl("jdbc:mysql:///xin");
			cpds.setUser("root");
			cpds.setPassword("java");
			cpds.setMaxPoolSize(20);
			cpds.setMinPoolSize(3);
			cpds.setInitialPoolSize(5);
		} catch (PropertyVetoException e) {
			e.printStackTrace();
		}
		
	}
	
	public static Connection getConnection(){
		try {
			if(cpds != null){
				return cpds.getConnection();
			}
		} catch (SQLException e) {
			e.printStackTrace();
		}
		return null;
	}
	
	
	private DBHelper(){
		
	}
	
	
	/**
	 * 关闭资源
	 * @param con 数据库连接
	 * @param rs 结果集
	 * @param ps 卡车
	 */
	public static void close(Connection con,ResultSet rs, PreparedStatement ps){
		try {
			if(ps != null){
				ps.close();
				ps = null;
			}
		} catch (SQLException e) {
			e.printStackTrace();
		}finally{
			try {
				if(rs != null){
					rs.close();
					rs = null;
				}
			} catch (SQLException e) {
				e.printStackTrace();
			}finally{
				try {
					if(con != null){
						con.close();
						con = null;
					}
				} catch (SQLException e) {
					e.printStackTrace();
				}
			}
		}
	}
	
	/**
	 * 执行增删改SQL语句
	 * @param sql
	 * @param params
	 * @return
	 */
	public static int executeUpdate(StringBuilder sql, Object...params){
		PreparedStatement ps = null;
		Connection con = null;
		int result = 0;
		try {
			con = getConnection();
			ps =  con.prepareStatement(sql.toString());
			for(int i = 0; i < params.length ; i ++){
				ps.setObject(i + 1, params[i]);
			}
			result = ps.executeUpdate();
		} catch (SQLException e) {
			e.printStackTrace();
		}finally{
			close(con, null, ps);
		}
		return result;
	}
	
	/**
	 * 执行查询的SQL语句
	 * @param sql
	 * @param params
	 * @return
	 */
	public static ResultSet executeQuery(Connection conn, StringBuilder sql, Object...params){
		PreparedStatement ps = null;
		ResultSet rs = null;
		try {
			ps =  conn.prepareStatement(sql.toString());
			for(int i = 0; i < params.length ; i ++){
				ps.setObject(i + 1, params[i]);
			}
			rs = ps.executeQuery();
		} catch (SQLException e) {
			e.printStackTrace();
		}
		return rs;
	}
}



我一直有一些疑问,现在我一一说出来,如果有好的关于这方面的文章,也希望也可以推荐给我,谢谢了!


比如我有两个类,它们之间是有主外键关系的:

public class Dept {
	private int id;
	private List<Employee> list;
	
	public int getId() {
		return id;
	}
	public void setId(int id) {
		this.id = id;
	}
	public List<Employee> getList() {
		return list;
	}
	public void setList(List<Employee> list) {
		this.list = list;
	}
}



public class Employee {

	private int id;
	private String name;
	private Dept dept;
	
	public int getId() {
		return id;
	}
	public void setId(int id) {
		this.id = id;
	}
	public String getName() {
		return name;
	}
	public void setName(String name) {
		this.name = name;
	}
	public Dept getDept() {
		return dept;
	}
	public void setDept(Dept dept) {
		this.dept = dept;
	}
}



这是我的员工Dao:
public class EmployeeDao {
	/**
	 * 根据员工的Id来查询对应的员工对象
	 * @param id
	 * @return
	 */
	public Employee find(int id){
		Connection conn = null;
		ResultSet rs = null;
		Employee e = null;
		try {
			StringBuilder sql = new StringBuilder("select * from t_employee where id = ?;");
			conn = DBHelper.getConnection();
			rs = DBHelper.executeQuery(conn, sql, id);
			if(rs.next()){
				e = new Employee();
				e.setId(rs.getInt("id"));
				e.setName(rs.getString("name"));
				//外键对象的查询
				e.setDept(new DeptDao().find(rs.getInt("dept_Id")));
			}
		} catch (SQLException ex) {
			ex.printStackTrace();
		} finally{
			DBHelper.close(conn, rs, null);
		}
		return e;
	}
	
	/**
	 * 根部部门Id来查询对应的部门下的所有员工
	 * @param id
	 * @return
	 */
	public List<Employee> findAll(int id){
		Connection conn = null;
		ResultSet rs = null;
		List<Employee> lists = new ArrayList<Employee>();
		try {
			StringBuilder sql = new StringBuilder("select * from t_employee where dept_Id = ?;");
			conn = DBHelper.getConnection();
			rs = DBHelper.executeQuery(conn, sql, id);
			Employee e = null;
			while(rs.next()){
				e = new Employee();
				e.setId(rs.getInt("id"));
				e.setName(rs.getString("name"));
				//外键对象的查询
				e.setDept(new DeptDao().find(rs.getInt("dept_Id")));
				lists.add(e);
			}
		} catch (SQLException ex) {
			ex.printStackTrace();
		} finally{
			DBHelper.close(conn, rs, null);
		}
		return lists;
	}
}
这是部门Dao:
public class DeptDao {
	/**
	 * 根据部门的Id查询对应的部门对象
	 * @param id
	 * @return
	 */
	public Dept find(int id){
		Connection conn = null;
		ResultSet rs = null;
		Dept d = null;
		try {
			StringBuilder sql = new StringBuilder("select * from t_dept where id = ?;");
			conn = DBHelper.getConnection();
			rs = DBHelper.executeQuery(conn, sql, id);
			if(rs.next()){
				d = new Dept();
				d.setId(rs.getInt("id"));
				d.setName(rs.getString("name"));
				//装载一对多的集合
				d.setList(new EmployeeDao().findAll(rs.getInt("id")));
			}
		} catch (SQLException e) {
			e.printStackTrace();
		} finally{
			DBHelper.close(conn, rs, null);
		}
		return d;
	}
}
这样写的Dao,在service层调用有这么几个问题:

首选就是死循环,这个我可以解决,让部门的Dao不去加载对应的员工对象集合就可以了。

其次就是连接中途被关闭的问题,比如,我调用查询单个员工的方法,由于员工对象在装载的过程中需要调用部门的Dao查询部门,而在部门的Dao中关闭了连接,这样会倒置员工对象继续查询报错。

最后我使用了数据库连接池,也解决了这个问题,但是我总感觉这不是良好的设计!!!

在这种场景下应该怎么去设计这些东西呢?

我有个思路:

组合查询,也就是多写几个Dao,有装载外键对象的,有不装载的,装载的使用组合查询,不知这样好是不好!

展开
收起
爱吃鱼的程序员 2020-06-22 14:20:16 603 0
1 条回答
写回答
取消 提交回答
  • https://developer.aliyun.com/profile/5yerqm5bn5yqg?spm=a2c6h.12873639.0.0.6eae304abcjaIB

    引用来自“李崇”的答案

    分开两个类来处理。

    Entity:和数据库中字段一一对应,比如你的员工和部门,是多对一关系,

    所以员工类里面有一个deptId即可。如果是多对多关系那就建立一个中间表的类。

    操作Entity中的字段就相当于操作数据库的字段。

    VO:用来做显示,和界面上的元素一一对应。

    UserVO{

      privateintid;

        privateStringdeptId;   

        privateStringdeptName;

    }

    差不多是这样,传入到Dao层的是Entity,传出的是VO,通过关联查询的SQL语句为VO赋值,传递回界面。

    你那种做法可能是用惯了hibernate的做法,不知道能不能理解。



    明白了!嗯,这样扩展性大大提高了!多谢啊!

    最好的设计--去掉pojo和DAO

    现在可能无法理解

    慢慢就理解了

    您的意思是不要考虑这些东西,直接考虑代码与数据库的关系是吗?拼接SQL,然后直接使用DBHelper执行?如果使用pojo与Dao,就要使用Hibernate是吗?最纯粹的才是最简便的,可惜我的场景就是这样的,头疼!

    分开两个类来处理。

    Entity:和数据库中字段一一对应,比如你的员工和部门,是多对一关系,

    所以员工类里面有一个deptId即可。如果是多对多关系那就建立一个中间表的类。

    操作Entity中的字段就相当于操作数据库的字段。

    VO:用来做显示,和界面上的元素一一对应。

    UserVO{

      privateintid;

        privateStringdeptId;   

        privateStringdeptName;

    }

    差不多是这样,传入到Dao层的是Entity,传出的是VO,通过关联查询的SQL语句为VO赋值,传递回界面。

    你那种做法可能是用惯了hibernate的做法,不知道能不能理解。



    谢谢你耐心的回答,我明白你的意思了,一个对应数据库,一个对应需求,不过我有个疑问。如果针对UserVO的业务比较复杂,也就是外键跨表字段比较多,是不是要在UserVO里面定义很多跨表的属性,有没有基于对象设计思想的解决方案啊!这样是不是扩展性有所欠缺,VO与页面绑死了!不知我说的对否!

    看了看楼主的设计,感觉挺合理的啊-。-有一个问题,不知楼主能否帮忙解答

    为什么 executeUpdate(StringBuildersql,Object...params)没有传入Connection对象,而 executeQuery(Connectionconn,StringBuildersql,Object...params)传入了呢?

    这样设计的道理是什么?

    这个很简单,1对多在多的一方设置一个外键字段,为了实现实体查询方便、添加一个List临时字段,查询的时候多的可以查询放入该List即可同理其他1对1多对多也一样,hibernate也是这样思想的嗯,你说的思路我明白,如果这样做,就要自己写一个类似Hibernate的懒加载机制,不然会死循环的

    引用来自“Timco”的答案

    看了看楼主的设计,感觉挺合理的啊-。-有一个问题,不知楼主能否帮忙解答

    为什么 executeUpdate(StringBuildersql,Object...params)没有传入Connection对象,而 executeQuery(Connectionconn,StringBuildersql,Object...params)传入了呢?

    这样设计的道理是什么?

    明白,我现在数据库的dao设计都是采用了楼主这样的设计。不过executeUpdate也传人了,因为这样可以在dao层同一connection进行事物控制,禁用autoCommit,等事物完成再提交。是啊,你说的很对,所以后续扩展了这个DBHelper类,重载了几个方法,也加入了执行事务的方法,执行存储过程的方法等等。
    2020-06-22 14:20:35
    赞同 展开评论 打赏
问答排行榜
最热
最新

相关电子书

更多
低代码开发师(初级)实战教程 立即下载
冬季实战营第三期:MySQL数据库进阶实战 立即下载
阿里巴巴DevOps 最佳实践手册 立即下载