论坛首页 Java企业应用论坛

(模板方法的运用)持久层操作的java代码

浏览 29433 次
精华帖 (1) :: 良好帖 (5) :: 新手帖 (6) :: 隐藏帖 (1)
作者 正文
   发表时间:2011-03-09   最后修改:2011-05-09
DAO

2008年刚从学校毕业就应聘到了一家外包小公司工作
对加拿大的客户开发一些web项目
公司里算我就2个人是做java的
于是我们变成了一个人身兼数个项目的程序员
经理只给我们客户的需求
然后数据库设计、编码等事你一个人搞定。。。

我当时刚出来对java的理解还没有那么深,没有商业项目的任何经验就让我搞着搞那。
当时连数据库连接的操作我都很苦恼,是用简单的jdbc还是hibernate呢,
最后为了方便而达到目的我选用了前者。赶项目的效率还是很重要的。

我给出当时自己写的操作数据库的代码
这家公司大多都是.net的程序所以用的数据库也为sql server 2005
所以不知道这样写是不是会有很多问题,因为我总觉得有问题。
3年后虽然已经离开公司但再回头看这些代码,我还是觉得有问题,所以必须发出来大家审视一下。

这些代码是2008年4月刚出道时自己写的。当时正在读老师推荐给我的java设计模式,看到了template这个模式,于是在工作中突然想到可以把这个模式应用在操作数据库的代码上
于是就产生了以下代码。




第一个是操作数据库方法的抽象接口,
其实这个接口也很简单,定义了那么几个方法,说白了就是操作数据库的。
为什么要写成泛型的接口,为了就是后面大家的业务有针对性,一个实体一个业务功能类。


package com.yd.idao;

import java.util.List;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;

import com.yd.support.JDataSet;

/**
 * 一个定义了所有我所需要的数据库操作的方法接口,
 * 为什么定义为抽象,我自己都搞不清楚,
 * 这里定义了泛型,这个很关键,你会看到泛型在这里的使用
 * @author kanny
 *
 * @param <T>
 */
public abstract interface ISqlHelper<T> {

	/**
	 * 执行sql语句,大多为单句插入语句
	 * @param sql	单句的sql语句
	 * @param params	插入的参数
	 * @param only		但为true时,sql语句为查询数量的查询语句
	 * @return
	 * @throws SQLException
	 */
	public boolean execute(String sql, Object[] params, boolean only) throws SQLException;

	/**
	 * 执行sql的批处理
	 * @param sqlBatch	多条sql语句
	 * @return
	 * @throws SQLException
	 */
	public boolean executeBatch(String[] sqlBatch) throws SQLException;

	/**
	 * 执行存储过程
	 * @param procName	存储过程名称
	 * @param params	存储过程说需要的参数
	 * @return
	 * @throws SQLException
	 */
	public boolean executeCall(String procName, Object[] params) throws SQLException;

	/**
	 * 查询一行数据封装成java的实体
	 * @param sql	sql语句
	 * @param params	sql条件参数
	 * @param viewName	视图名字,在查询多表关联的数据时用于区分
	 * @param executeCall	是否是存储过程,如果为true,第一个sql的参数为存储过程名称
	 * @return
	 * @throws SQLException
	 */
	public T find(String sql, Object[] params, String viewName, boolean executeCall) throws SQLException;

	/**
	 * 查询多行数据封装成java的实体加入一个List里
	 * @param sql	sql语句
	 * @param params	sql条件参数
	 * @param viewName	视图名字,在查询多表关联的数据时用于区分,循环封装实体比单一的石头封装要复杂
	 * @param executeCall	是否是存储过程,如果为true,第一个sql的参数为存储过程名称
	 * @return
	 * @throws SQLException
	 */
	public List<T> findList(String sql, Object[] params, String viewName, boolean executeCall) throws SQLException;

	/**
	 * 为了方便操作,我还特意写定义了这个返回ResultSet的方法,便于直接操作
	 * @param sql
	 * @param params
	 * @param executeCall
	 * @return
	 * @throws SQLException
	 */
	public ResultSet returnResultSet(String sql, Object[] params, boolean executeCall) throws SQLException;
	
	/**
	 * 我的底层分页方法,我会给出具体代码,但这里用的只是sql server 2008的数据库分页
	 * @param sql	
	 * @param orderby	排序列
	 * @param currentPage	当前页
	 * @param pageSize	每页多少行
	 * @return
	 * @throws SQLException
	 */
	public List<T> findListAsPager(String sql, String orderby, int currentPage, int pageSize) throws SQLException;

	/**
	 * 后来为了方便操作,想朋友要来了这个JDataSet类,类似.net的DataTable的作用
	 * @param sql
	 * @param params
	 * @param fillColumnNames	是否填充类名,请看方法代码
	 * @param executeCall
	 * @return
	 */
	public JDataSet getJDataSet(String sql, Object[] params, boolean fillColumnNames, boolean executeCall);
	
	/**
	 * 由于有了JDataSet这个类,于是我有写了调用这个类的分页方法
	 * @param sql
	 * @param orderby
	 * @param currentPage
	 * @param pageSize
	 * @return
	 * @throws SQLException
	 */
	public JDataSet getJDataSetAsPager(String sql, String orderby, int currentPage, int pageSize) throws SQLException;
	
	/**
	 * 为了方便起见,我多写了一个直接传入ResultSet而封装JDataSet的多余方法
	 * @param rs
	 * @param fillColumnNames
	 * @return
	 * @throws SQLException
	 */
	public JDataSet loadJDataSet(ResultSet rs, boolean fillColumnNames) throws SQLException;
	
	/**
	 * 得到查询数据的行数
	 * @param sql
	 * @return
	 * @throws SQLException
	 */
	public int getRowCount(String sql) throws SQLException;
	
	/**
	 * 请看源码
	 * @param rs
	 * @param column
	 * @return
	 * @throws SQLException
	 */
	public String changeFont(ResultSet rs, String column) throws SQLException;
	
	/**
	 * 得到连接
	 * @return
	 * @throws SQLException
	 */
	public Connection returnConn() throws SQLException; 
	
	/**
	 * 清楚所有数据库操作对象
	 * @throws SQLException
	 */
	public void clearAllsql() throws SQLException; 

}




第2个类是实现这个抽象接口的抽象模版方法类
这个类最为关键,它肯定是实现了ISqlHepler.java里面的所有的方法。

其中:

protected abstract T loadDataBean(ResultSet rs, String viewName) throws SQLException;

protected abstract T loadDataBeanSelf(ResultSet rs) throws SQLException;

这2个方法是SqlHelper.java里留出来了,就是为了大家可以自己封装实体javabean来用

package com.yd.dao;

import java.sql.*;
import java.util.*;

import com.yd.db.DBConnPoolMgr;
import com.yd.idao.ISqlHelper;
import com.yd.support.JDataSet;

public abstract class SqlHelper<T> implements ISqlHelper<T> {

	protected java.sql.Connection conn = null;

	protected java.sql.PreparedStatement pst = null;

	protected java.sql.Statement st = null;

	protected java.sql.CallableStatement cs = null;

	protected java.sql.ResultSet rs = null;

	protected java.sql.ResultSetMetaData rm = null;
	
	public Connection returnConn() throws SQLException {

                  //DBConnPoolMgr是自己写的一个简单连接池类,用来得到连接,我后面会给出这个类的代码
		return (conn = DBConnPoolMgr.getInctence().getConnect());
	}
	
	private PreparedStatement returnPst(String sql, Object[] params) throws SQLException {

		if (conn == null || conn.isClosed()) conn = returnConn();
		pst = conn.prepareStatement(sql);
		if (params != null)
			for (int i = 0; i < params.length; i++)
				pst.setObject(i + 1, params[i]);
		return pst;
		
	}

	protected CallableStatement returnCs(String procName, Object[] params) throws SQLException {

		if (conn == null || conn.isClosed()) conn = returnConn();
		String call = "";
		if (params != null) {
			call = "{call " + procName + "(";
			for (int c = 0; c < params.length - 1; c++)
				call += "?,";
			call += "?)}";
		} else
			call = "{call " + procName + "()}";
		cs = conn.prepareCall(call);
		if (params != null)
			for (int i = 0; i < params.length; i++)
				cs.setObject(i + 1, params[i]);
		return cs;
		
	}

	public void clearAllsql() {
		try 
		{ 
			if (rs != null) rs.close();
			if (cs != null) cs.close();
			if (st != null) st.close();
			if (pst != null) pst.close();
			if (conn != null) {
				DBConnPoolMgr.getInctence().returnConnect(conn);
			}
			rs = null;
			cs = null;
			st = null;
			pst = null;
		} 
		catch (SQLException ex) { ex.printStackTrace(); }
	}

	public boolean execute(String sql, Object[] params, boolean only) {
		
		boolean bVal = false;
		try 
		{
			if (only) {
				rs = returnPst(sql, params).executeQuery();
				while (rs.next()) bVal = true;
			} else {
				returnPst(sql, params).executeUpdate();
				bVal = true;
			}
		} 
		catch (SQLException ex) { ex.printStackTrace(); } 
		finally { clearAllsql(); }
		return bVal;
	}

	public boolean executeBatch(String[] sqlBatch) {
		
		boolean bVal = false;
		try 
		{
			conn = returnConn();
			st = conn.createStatement();
			boolean autoCommit = conn.getAutoCommit();

			for (int i = 0; i < sqlBatch.length; i++) {
				if (sqlBatch[i] != null && !sqlBatch[i].equals(""))
					st.addBatch(sqlBatch[i] + ";");
			}
			conn.setAutoCommit(false);
			st.executeBatch();
			conn.commit();
			conn.setAutoCommit(autoCommit);
			bVal = true;
		} 
		catch (SQLException ex) {
			try { conn.rollback(); } catch (SQLException e) { e.printStackTrace(); }
			ex.printStackTrace();
		} finally { clearAllsql(); }
		return bVal;
	}

	public boolean executeCall(String procName, Object[] params) {
		
		boolean bVal = false;
		try 
		{
			returnCs(procName, params).executeUpdate();
			bVal = true;
		} 
		catch (Exception ex) { ex.printStackTrace(); } 
		finally { clearAllsql(); }
		return bVal;
	}

	public T find(String sql, Object[] params, String viewName, boolean executeCall) {
		
		T t = null;
		try 
		{
			if (executeCall) rs = returnCs(sql, params).executeQuery();
			else rs = returnPst(sql, params).executeQuery();
			t = loadResultSet(rs, viewName);
		} 
		catch (Exception ex) { ex.printStackTrace(); } 
		finally { clearAllsql(); }
		return t;
	}

	public List<T> findList(String sql, Object[] params, String viewName, boolean executeCall) {
		
		List<T> lt = null;
		try 
		{
			if (executeCall) rs = returnCs(sql, params).executeQuery();
			else rs = returnPst(sql, params).executeQuery();
			lt = loadList(rs, viewName);
		} 
		catch (Exception ex) { ex.printStackTrace(); } 
		finally { clearAllsql(); }
		return lt;
	}

	public ResultSet returnResultSet(String sql, Object[] params, boolean executeCall) {
		try 
		{
			if (executeCall) rs = returnCs(sql, params).executeQuery();
			else rs = returnPst(sql, params).executeQuery();
		} 
		catch (Exception ex) { ex.printStackTrace(); }
		return rs;
	}

	private T loadResultSet(ResultSet rs, String viewName) throws SQLException {
		
		T t = null;
		if (rs != null) while (rs.next()) t = loadDataBean(rs, viewName);
		return t;
	}

	private List<T> loadList(ResultSet rs, String viewName) throws SQLException {
		
		List<T> tlist = new ArrayList<T>();
		if (rs != null) 
			while (rs.next()) 
				tlist.add(loadDataBean(rs, viewName));
		return tlist;
	}

	public String changeFont(ResultSet rs, String column) throws SQLException {
		return rs.getString(column) == null ? "" : rs.getString(column);
	}

	public int returnColumnCount(ResultSet rs) throws SQLException {
		return rs.getMetaData().getColumnCount();
	}

         //两个非常关键的模版方法,继承此类的操作类都要实现这2个方法,我到时候会给出操作类
	protected abstract T loadDataBean(ResultSet rs, String viewName) throws SQLException;

	protected abstract T loadDataBeanSelf(ResultSet rs) throws SQLException;

	public List<T> findListAsPager(String sql, String orderby, int currentPage, int pageSize) {
		
		List<T> lt = null;
		try 
		{
			String strVal = strPager(sql, orderby, currentPage, pageSize);
			rs = returnPst(strVal, null).executeQuery();
			lt = loadList(rs, "");
		} 
		catch (SQLException ex) { ex.printStackTrace(); } 
		finally { clearAllsql(); }
		return lt;
	}

         //因为用的sql server 2008所以只写了这个数据库的分页
	private String strPager(String sql, String orderby, int currentPage, int pageSize) {
		
		int start = 1;
		if (currentPage > 1) start = (currentPage - 1) * pageSize + 1;
		int end = start + pageSize - 1;
		
		String sqlColumn = "*";
		String sqlDo = "";
		if(sql.indexOf("@#") != -1) {
			String []sqlArr = sql.split("@#");
			sqlColumn = sqlArr[0];
			sqlDo = sqlArr[1];
		} else {
			sqlColumn = "*";
			sqlDo = sql;
		}
		
		String strVal = "select * from (select " + sqlColumn + ",ROW_NUMBER()";
		strVal += " Over(order by " + orderby + ")";
		strVal += " as rowNum " + sqlDo + ")";
		strVal += " as myTable where rowNum between " + start + " and " + end;

		return strVal;
	}

	public int getRowCount(String sql) throws SQLException {
		
		String sqlDo = "";
		if(sql.indexOf("@#") != -1) {
			String []sqlArr = sql.split("@#");
			sqlDo = sqlArr[1];
		} else sqlDo = sql;
		
		int count = 0;
		try 
		{
			rs = this.returnResultSet("select count(*) " + sqlDo, null, false);
			while (rs.next()) count = rs.getInt(1);
		} 
		catch (Exception ce) { ce.printStackTrace();} 
		finally { clearAllsql(); }
		return count;
	}

	public JDataSet getJDataSetAsPager(String sql, String orderby, int currentPage, int pageSize) {
		
		String strVal = strPager(sql, orderby, currentPage, pageSize);
		return getJDataSet(strVal, null, true, false);
	}

	public JDataSet getJDataSet(String sql, Object[] params, boolean fillColumnNames, boolean executeCall) {
		
		JDataSet jds = null;
		try 
		{
			if (executeCall) rs = returnCs(sql, params).executeQuery();
			else rs = returnPst(sql, params).executeQuery();
			jds = loadJDataSet(rs, fillColumnNames);
		} 
		catch (Exception ex) { ex.printStackTrace(); } 
		finally { clearAllsql(); }
		return jds;
	}
	
	public JDataSet loadJDataSet(ResultSet rs, boolean fillColumnNames) {
		
		JDataSet jds = new JDataSet();
		try 
		{
			int columnCount = returnColumnCount(rs);
			if (fillColumnNames) {
				String[] columnNames = new String[columnCount];
				String[] columnTypeNames = new String[columnCount];
				for (int i = 0; i < columnCount; i++) {
					columnNames[i] = rs.getMetaData().getColumnName(i + 1);
					columnTypeNames[i] = rs.getMetaData().getColumnTypeName(i + 1);
				}
				jds.setColumnNames(columnNames);
				jds.setColumnTypeNames(columnTypeNames);
			}
			while (rs.next()) {
				String[] row = new String[columnCount];
				for (int i = 0; i < columnCount; i++)
					row[i] = rs.getString(i + 1) == null ? "" : rs.getString(i + 1).trim();
				jds.addRow(row);
			}
		} 
		catch (Exception ex) { ex.printStackTrace(); }
		return jds;
	}

}




现在来说下怎么用这个类
比如,你有一个学生类Student.java里面有id,name,age3个属性


public Class Student
{
	private int id;
	private String name;
	private int age;
	
	以下get和set方法省略...
}



你要封装它,你就可以自己定义一个IStudent.java接口extends那个ISqlHelper.java接口
然后来里面定义插入,查询全部,和按id查的功能函数,象如下写的一样
<

当然这样定义很死,最好的方法,就是整理抽象出所有业务类的公用方法,比如,插入,修改,删除。
然后把这些方法在一个公用的操作接口中定义,这个公用的操作接口可以继承ISqlHelper.java接口
然后以后业务类或是数据实体操作类都可以实现这个公用的操作接口。

>


public interface IStudent extends ISqlHelper<Student>
{
	//插入Student
	public boolean insertStudent(Student stu);

	//查询全部Student
	public List<Student> findStudent();

	//按id查询某一个Student
	public Student findStudentAsId(int stuid);
}




并写一个StudentBO.java业务类extends那个SqlHelper和自己定义的IStudent接口
象如下写的一样:


public Class StudentBO extends SqlHelper<Student> implements IStudent
{
	
	protected Student loadDataBean(ResultSet rs, String viewName) throws SQLException;
	{
		Student stu = loadDataBeanSelf(ResultSet rs);
		return stu;
	}

	protected Student loadDataBeanSelf(ResultSet rs) throws SQLException;
	{
		Student stu = new Student();
		stu.setId(rs.getInt("id"));
		stu.setName(rs.changeFont(rs,"name"));
		stu.setAge(rs.getInt("age"));
		
		return stu;
	}

	//实现:插入Student的函数
	public boolean insertStudent(Student stu)
	{
		String sql = "insert into Student(id,name,age) values(?,?,?)";
		Object [] params = new Object[]{stu.getId(),stu.getName,stu.getAge};
		return this.execute(sql, params, false);
	}

	//实现:查询全部Student
	public List<Student> findStudent()
	{
		String sql = "select * from Student";
		return this.findList(sql, null, "",false);
	}

	//实现:按id查询某一个Student
	public Student findStudentAsId(int stuid)
	{
		String sql = "select * from Student where id=" + stuid;
		return this.find(sql, null, "",false);
	}
}



loadDataBean,loadDataBeanSelf是从SqlHelper里继承下来的抽象方法。
必须实现。这就是利用了模版方法模式写出来的。
也就是说,以后你自己写函数的时候,只要在接口里定义,
然后在这样的BO类里写实现它,而且就写那么一点语句就完成了。大大省时省力。

好了这样就完成了,代码是不是看上去又清楚又整洁,如果查找和维护。而且大大减少了那些功能代码和封装象Student.java这样数据实体的代码。一次封装无须在写。

大家肯定有疑问,那就是
this.execute
this.findList
this.find
三个函数哪来的,里面都做了些什么
首先要说他们3个肯定是SqlHelper里被实现过的函数
至于他们怎么实现的,你仔细看下就全明白了。


为什么要多出一个loadDataBean来,大家都看到了,他有一个参数viewName,这里就是用到这个参数的地方,因为大家操作数据库不可能就是那么一张表的操作,有时候会有2到3张表联查,那么用这个参数来判断是哪个函数查出不一样的结果,那么你在这个函数里利用这个参数引进别的xxxBO的loadDataBeanSelf函数,不就可以不用再次重新写那些讨厌的实体封装代码了吗。重用性大大提升。

注意一点,这里的查询是只能全查,如果只需要几个字段,就只能手动自己写咯,其实我是想写一个通用的想查几个字段就几个字段的封装方法,但我懒,所以到现在都没去写

其实还有很多应用和问题,这些我在这里也说不完,希望大家多多交流。


我还会给出我自己当初写的数据库连接池类,希望这些简单的代码能给java新手以帮助。
而且如果大家觉得有问题有意义,一定要发帖指出。哪里好哪里不好
希望大家多多给出看法,多多益善。

在这里还要感谢论坛中yangguo兄,其实我当时写这段代码的主要目的
就是dbutils框架所实现的一个最重要功能
只提交sql语句,然后的数据自动填充到所需要的实体中。而不要在写那么多繁琐的实体封装代码。


这个是源码例子,希望大家能明白这样写的用意,值得探讨。
   发表时间:2011-03-09   最后修改:2011-03-10
给出JDataSet类,此类可以说是一个数据结构类
package com;

import java.io.Serializable;
import java.util.*;

public class JDataSet implements Serializable {

	private String[] columnNames, columnTypeNames;
	private List<String[]> table, tableFlag;

	public JDataSet(String[] columnNames, String[] columnTypeNames) {
		super();
		this.table = new ArrayList<String[]>();
		this.columnNames = columnNames;
		this.columnTypeNames = columnTypeNames;
	}

	public JDataSet() {
		super();
		this.table = new ArrayList<String[]>();
	}
	
	public int getRowCount() {
		return table.size();
	}

	public String[] getColumnNames() {
		return columnNames;
	}

	public void setColumnNames(String[] columnNames) {
		this.columnNames = columnNames;
	}

	public String[] getColumnTypeNames() {
		return columnTypeNames;
	}

	public void setColumnTypeNames(String[] columnTypeNames) {
		this.columnTypeNames = columnTypeNames;
	}

	public List<String[]> getTable() {
		if (table == null)
			table = new ArrayList<String[]>();
		return table;
	}

	public int getColumnCount() {
		if (columnNames != null) { return columnNames.length; } 
		else { if (!table.isEmpty()) { return table.get(0).length; } }
		return 0;
	}

	public String[] getRow(int rowIndex) {
		rowRangeCheck(rowIndex);
		return table.get(rowIndex);
	}

	private void rowRangeCheck(int rowIndex) {
		if (rowIndex < 0 || rowIndex >= table.size()) {
			throw new IllegalArgumentException("rowIndex overflow : " + rowIndex);
		}
	}

	private void columnRangeCheck(int columnIndex) {
		if (columnIndex < 0 || columnIndex >= columnNames.length) {
			throw new IllegalArgumentException("columnIndex overflow : ????" + columnIndex);
		}
	}

	public void addRow(String[] row) {
		table.add(row);
	}

	public void addRow(String[] row, String[] flag) {
		table.add(row);
		tableFlag.add(flag);
	}

	public void removeRow(int rowIndex) {
		table.remove(rowIndex);
	}

	public void clearRow(int rowIndex) {
		table.set(rowIndex, null);
	}

	public void removeFullIndex(int rowIndex) {
		table.remove(rowIndex);
		tableFlag.remove(rowIndex);
	}

	public void setCell(int rowIndex, int columnIndex, String value) {
		rowRangeCheck(rowIndex);
		String[] row = table.get(rowIndex);
		columnRangeCheck(columnIndex);
		row[columnIndex] = value;
	}

	public void setCellFlag(int rowIndex, int columnIndex, String value) {
		rowRangeCheck(rowIndex);
		String[] row = tableFlag.get(rowIndex);
		columnRangeCheck(columnIndex);
		row[columnIndex] = value;
	}

	public void setCell(int rowIndex, String columnName, String value) {
		int columnIndex = indexof(columnName.trim());
		if (columnIndex > -1) {
			setCell(rowIndex, columnIndex, value);
		}
	}

	public String getCell(int rowIndex, int columnIndex) {
		rowRangeCheck(rowIndex);
		String[] row = table.get(rowIndex);
		columnRangeCheck(columnIndex);
		return row[columnIndex];
	}

	public String getCellFlag(int rowIndex, int columnIndex) {
		rowRangeCheck(rowIndex);
		String[] row = tableFlag.get(rowIndex);
		columnRangeCheck(columnIndex);
		return row[columnIndex];
	}

	public String getCell(int rowIndex, String columnName) {
		int columnIndex = indexof(columnName.trim());
		if (columnIndex > -1) {
			return getCell(rowIndex, columnIndex);
		}
		return "";
	}

	public String[] getColumn(int columnIndex) {
		columnRangeCheck(columnIndex);
		String[] column = new String[getRowCount()];
		for (int i = 0; i < column.length; i++) {
			column[i] = table.get(i)[columnIndex];
		}
		return column;
	}

	public String[] getColumn(String columnName) {
		int columnIndex = indexof(columnName.trim());
		if (columnIndex > -1) {
			return getColumn(columnIndex);
		}
		return null;
	}

	private int indexof(String columnName) {
		for (int i = 0; i < columnNames.length; i++) {
			if (columnNames[i].trim().equalsIgnoreCase(columnName))
				return i;
		}
		return -1;
	}
}
0 请登录后投票
   发表时间:2011-03-10  
三年前写的,真的不错,三年前我还在写jdbc呢
0 请登录后投票
   发表时间:2011-03-10  
挺好。比我刚出来时写的好。。。
1 请登录后投票
   发表时间:2011-03-10  
07年就用上了sqlserver2008啊
0 请登录后投票
   发表时间:2011-03-10  
写的不错啊 ,我刚出道,啥都不会呢
0 请登录后投票
   发表时间:2011-03-10  
如果一切思想都是楼主原创( 没看过其他的JDBC框架),3年时间有这样的设计能力,很牛叉!
0 请登录后投票
   发表时间:2011-03-10  
08年的公司有用sql server 2008的吗?  太前卫了。
0 请登录后投票
   发表时间:2011-03-10  
刚出来就能写得这样、我表示不蛋定了/
0 请登录后投票
   发表时间:2011-03-10  
.net味道太重,DAO层接口设计跟.net的API差不多。
0 请登录后投票
论坛首页 Java企业应用版

跳转论坛:
Global site tag (gtag.js) - Google Analytics