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

_-Leon-_ 发布于 2013/11/21 10:12
阅读 670
收藏 2

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

我在开发一个项目,没有使用框架,技术选型使用的是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,有装载外键对象的,有不装载的,装载的使用组合查询,不知这样好是不好!

加载中
0
蕃薯哥哥
蕃薯哥哥

引用来自“李崇”的答案

分开两个类来处理。

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

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

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

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

UserVO{

    private int id;

    private String deptId;    

    private String deptName;

}

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

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



这里说的VO并不是说整个页面只对应一个VO,比如说现在咱们看的这个页面,评论的部分对应一个CommentVO,评论时会显示出你的名字,这时候CommentVO里面就有一个CommenterName属性,来保存你的名字。

再比如页面的右半部分是一个类似的话题 这样的版块。这就是另一个TopicVO了。页面上的不同模块hi对应不同的VO。

往页面放的时候request.setAttribute("commentInfo" , commentVO);

request.setAttribute("topicInfo" , topicVO);

_-Leon-_
_-Leon-_
明白了!嗯,这样扩展性大大提高了!多谢啊!
0
宏哥
宏哥

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

现在可能无法理解

慢慢就理解了

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

分开两个类来处理。

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

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

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

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

UserVO{

    private int id;

    private String deptId;    

    private String deptName;

}

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

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



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

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

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

这样设计的道理是什么?

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

引用来自“Timco”的答案

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

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

这样设计的道理是什么?

因为 executeUpdate方法是用来执行增删改的,执行完后就结束了,所以在该方法中直接从DBHelper自己的属性中获取连接,用完然后关掉。 但是 executeQuery 方法执行完查询后并没有结果,因为返回的ResultSet要在Dao中进一步使用,如果直接关闭会导致ResultSet不可用,但是又不能不关闭,所以就让Dao自己去获取连接,然后用完之后也在Dao中关闭连接。不知道我说清楚没。
0
Timco
Timco
明白,我现在数据库的dao设计都是采用了楼主这样的设计。不过executeUpdate也传人了,因为这样可以在dao层同一connection进行事物控制,禁用autoCommit,等事物完成再提交。
_-Leon-_
_-Leon-_
是啊,你说的很对,所以后续扩展了这个DBHelper类,重载了几个方法,也加入了执行事务的方法,执行存储过程的方法等等。
返回顶部
顶部