首页 > 代码库 > JAVA实现DAO层基本CRUD操作
JAVA实现DAO层基本CRUD操作
随着shh2框架各种操作的便利性,越来越多的JAVA WEB开发人员选择通过加入这些框架以提高开发效率,但是,如果在不了解这些框架使用的场合的情况下,一拿到项目就盲目地选择这些框架进行系统架构的搭建,就有可能造成很多没必要的资源浪费。
在项目开发中,对数据库的CRUD操作我们一般都是无法避免的操作,虽然hibernate封装的很完美,但是,由于本人对这个框架的底层原理不是很了解,每次使用的时候心里总觉得没底,代码一旦出现异常,很多时候都没法快速有效地解决,因此,为了让代码异常处理风险控制在自己的可控范围内,还是决定先将数据的CRUD持久化操作(DAO)用自己的方式通过JDBC进行一次封装,以便更好地管理自己的代码。关于Hibernate框架的使用,还是先弄懂它的一些底层实现原理后,再根据项目的需要酌情选择使用。
下面具体讲讲通过我自己的方式对有关DAO层数据库基本CRUD操作的JAVA实现(此处已MySQL为例,其他数据库只需做部分修改即可)。
备注:若要试用本示例,只需按照给出的顺序依次复制代码建立相应的类即可。另外,在项目lib目录下加入mysql链接jar包。
(1)定义数据源常量类
package com.jkitn.jkits.common; /** * 定义数据源常量类 * @author xdweleven * @version 1.0 */ public class JdbcConfig { /** 数据库驱动 */ public static final String DRIVERCLASSNAME = "com.mysql.jdbc.Driver"; /** 数据库URL */ public static final String URL = "jdbc:mysql://localhost:3306/app_jkit"; /** 数据库用户名 */ public static final String USERNAME = "root"; /** 数据库密码 */ public static final String PASSWORD = "root"; }
(2)定义结果集(ResultSet)到pojo对象的映射
package com.jkitn.jkits.dao.common; import java.lang.reflect.Field; import java.sql.ResultSet; import java.sql.ResultSetMetaData; import java.sql.SQLException; import java.util.ArrayList; import java.util.List; /** * 说明:实现结果集到pojo对象的映射 * @author xdweleven * @version 1.0 */ public class RowMapper<T>{ private Class<T> objectClass; public RowMapper(Class<T> objectClass) { this.objectClass = objectClass; } /** * 实现单条记录到对象的映射 * @param rs 结果集 * @param rowNum 当前行数 * @return * @throws SQLException */ public T mapRow(ResultSet rs, int rowNum) throws SQLException { try { T object = objectClass.newInstance(); // 得到结果集的字段集合 ResultSetMetaData metaData = http://www.mamicode.com/rs.getMetaData();> (3)定义数据库连接辅助类DBConnpackage com.jkitn.jkits.common; import java.sql.Connection; import java.sql.DriverManager; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; import java.util.List; import com.jkitn.jkits.dao.common.RowMapper; /** * 定义数据库连接辅助类DBConn * @author xdweleven * @version 1.0 */ public class DBConn { private Connection conn = null; private PreparedStatement pstmt = null; private ResultSet rs = null; /** * 创建数据库的连接 * @return 返回数据库连接对象 */ public Connection getConn(){ try { // 加载数据库驱动 Class.forName(JdbcConfig.DRIVERCLASSNAME); // 创建Connection接口对象,用于获取MySQL数据库的连接对象 conn = DriverManager.getConnection(JdbcConfig.URL, JdbcConfig.USERNAME, JdbcConfig.PASSWORD); } catch (ClassNotFoundException e) { e.printStackTrace(); } catch (SQLException e) { e.printStackTrace(); } return conn; } /** * 更新数据库操作(包括增删改操作) * @param sql 待执行sql语句 * @param objs 用于设置预编译语句中带入的参数 * @return null * @throws Exception */ public int execUpdate(String sql, Object ...objs) throws Exception{ // 获取预编译环境 pstmt = this.getConn().prepareStatement(sql); if(objs != null && objs.length > 0){ for(int i = 0; i < objs.length; i++){ pstmt.setObject(i+1, objs[i]); } } // 执行更新语句 int result = pstmt.executeUpdate(); // 断开连接,释放资源 this.close(rs, pstmt, conn); return result; } /** * 数据库查询操作 * @param sql 待执行sql语句 * @param objs 用于设置预编译语句中带入的参数 * @return 类T的List数据类型,即返回查询到的所有数据信息 * @throws Exception */ public <T> List<T> execQuery(String sql, RowMapper<T> mapper, Object ...objs) throws Exception{ // 获取预编译环境 pstmt = this.getConn().prepareStatement(sql); if(objs != null && objs.length > 0){ for(int i = 0; i < objs.length; i++){ pstmt.setObject(i+1, objs[i]); } } // 执行更新语句 rs = pstmt.executeQuery(); // 执行关系到对象的映射 List<T> result = mapper.mapRows(rs); // 断开连接,释放资源 this.close(rs, pstmt, conn); return result; } /** * 执行执行的SQL语句并返回结果集 * @param sql SQL语句 * @param params 参数 * @return 返回结果集 */ public ResultSet queryForResultSet(String sql, Object... objs) throws Exception{ // 获取预编译环境 pstmt = conn.prepareStatement(sql); if(objs != null && objs.length > 0){ for(int i = 0; i < objs.length; i++){ pstmt.setObject(i+1, objs[i]); } } // 执行更新语句 rs = pstmt.executeQuery(); // 断开连接,释放资源 this.close(null, pstmt, conn); return rs; } /** * 关闭数据库连接,释放所占的系统资源 * @param rs结果集、ppst预编译命令、conn数据库 * @return null * @throws Exception */ public void close(ResultSet rs, PreparedStatement ppst, Connection conn) throws Exception{ if(rs != null){ rs.close(); } if(ppst != null){ ppst.close(); } if(conn != null){ conn.close(); } } }(4)定义通用分页查询实体类
package com.jkitn.jkits.common; import java.util.List; /** * 说明:实现通用分页查询实体类 * @author xdweleven * @version 1.0 */ public class PageBean<T> { private int totalRows; // 总记录数 private int totalPages; // 总页数 private int curPage; // 当前页码 private int prePage; // 上一页页码 private int nextPage; // 下一页页码 private int rowsPerPage; // 每页显示的记录数 private List<T> pageList; // 当前页的数据集 /** * 初始化分页bean对象 * @param totalRows 总记录数 * @param rowsPerPage 每页显示的记录数 */ public void initPageBean(int totalRows, int rowsPerPage){ this.rowsPerPage = rowsPerPage; this.totalRows = totalRows; this.totalPages = (this.totalRows-1)/this.rowsPerPage + 1; // 设置上一页 if(this.curPage == 1){ this.setPrePage(1); }else{ this.setPrePage(this.curPage-1); } // 设置下一页 if(this.curPage == this.totalPages){ this.setNextPage(this.totalPages); }else{ this.setNextPage(this.curPage + 1); } } /** * 生成SQLServer的分页查询语句 * @param sql 原始sql语句 * @param curPage 第几页 * @param rowsPerPage 每页多少行 */ public String getPageSQLServer(String sql, int curPage, int rowsPerPage){ String afterFrom = sql.toLowerCase().substring(sql.indexOf("from")); String pageSql = null; if(afterFrom.indexOf("where") == -1){ pageSql = "select top "+ rowsPerPage + " * "+afterFrom +" where id not in(select top "+rowsPerPage*(curPage-1)+" id " +afterFrom+" order by id desc)"+"order by id desc"; }else{ pageSql = "select top "+ rowsPerPage + " * "+afterFrom +" and id not in(select top "+rowsPerPage*(curPage-1)+" id " +afterFrom+" order by id desc)"+"order by id desc"; } return pageSql; } /** * 生成MySql分页sql语句 * @param sql 原始sql语句 * @param curPage 第几页 * @param rowsPerPage 每页多少行 * @return 返回分页SQL语句 */ public String getPageMySQL(String sql, int curPage, int rowsPerPage){ String pageSql = sql+" limit "+ (curPage-1)*rowsPerPage+","+rowsPerPage; return pageSql; } /** * 生成Oracle分页查询语句 * @param sql 原始sql语句 * @return 返回分页SQL语句 */ public String getOrclPageSql(String sql){ int begin = (curPage - 1) * rowsPerPage; int end = begin + rowsPerPage; StringBuffer pagingSelect = new StringBuffer(300); pagingSelect.append("select * from ( select row_.*, rownum rownum_ from ( "); pagingSelect.append(sql); pagingSelect.append(" ) row_ where rownum <= "+end+") where rownum_ > "+begin); return pagingSelect.toString(); } public List<T> getPageList() { return pageList; } public void setPageList(List<T> pageList) { this.pageList = pageList; } public int getTotalRows() { return totalRows; } public void setTotalRows(int totalRows) { this.totalRows = totalRows; } public int getTotalPages() { return totalPages; } public void setTotalPages(int totalPages) { this.totalPages = totalPages; } public int getCurPage() { return curPage; } public void setCurPage(int curPage) { this.curPage = curPage; } public int getPrePage() { return prePage; } public void setPrePage(int prePage) { this.prePage = prePage; } public int getNextPage() { return nextPage; } public void setNextPage(int nextPage) { this.nextPage = nextPage; } public int getRowsPerPage() { return rowsPerPage; } public void setRowsPerPage(int rowsPerPage) { this.rowsPerPage = rowsPerPage; } }(5)定义SQL语句的常用方法工具类
package com.jkitn.jkits.util; import java.lang.reflect.Field; import java.util.ArrayList; import java.util.Date; import java.util.HashMap; import java.util.List; import java.util.Map; /** * 说明:自动生成对象的增删改查SQL语句的通用方法工具类 * @author xdweleven * @version 1.0 */ public class SQLUtil { /** * 自动生成插入指定对象的SQL语句,不包括对象属性的值为空的字段 * @param obj 待生成插入SQL语句的对象 * @param tableName 待插入语句对应的数据库表的名称 * @return 返回一个包含SQL语句、SQL语句参数值及参数值类型的Map对象 */ public static Map<String, Object> generateInsertExceptNull(Object obj, String tableName) { StringBuffer columnStrBuf = new StringBuffer(); // 记录数据表的字段名称 StringBuffer paramStrBuf = new StringBuffer(); // 记录SQL语句对应插入的占位符 List<Object> paramValues = new ArrayList<Object>(); // 记录对象参数值 List<Integer> paramsType = new ArrayList<Integer>(); // 记录参数值类型 // 查询待插入对象的属性值不为空的属性名称 List<Object> fieldList = ReflectionUtil.getNotNullField(obj); try { for (int i = 0; i < fieldList.size(); i++) { Field field = (Field) fieldList.get(i); field.setAccessible(true); // 记录对象属性名称 columnStrBuf.append(field.getName()); if (i != fieldList.size() - 1) { columnStrBuf.append(","); } // 记录插入SQL语句的参数占位符 if("class java.util.Date".equals(field.getType().toString()) && field.get(obj) != null){ String timeStr = DateUtil.formatDate((Date)field.get(obj), "yyyy-MM-dd HH:mm:ss"); paramStrBuf.append("to_date(?, 'yyyy-MM-dd HH24:mi:ss')"); paramValues.add(timeStr); // 记录对象属性的数据类型 paramsType.add(getOrclDataType(field.getType().toString())); }else{ paramStrBuf.append("?"); paramValues.add(field.get(obj)); // 记录对象属性的数据类型 paramsType.add(getOrclDataType(field.getType().toString())); } if (i != fieldList.size() - 1) { paramStrBuf.append(","); } } } catch (Exception e) { throw new RuntimeException(e); } // 生成插入操作的SQL语句 StringBuffer sb = new StringBuffer(); sb.append("insert into "); sb.append(tableName); sb.append(" ("); sb.append(columnStrBuf); sb.append(") "); sb.append("values"); sb.append(" ("); sb.append(paramStrBuf); sb.append(")"); // 将生成的SQL语句、SQL语句参数值及各参数值的数据类型用map保存并返回 Map<String, Object> sqlMap = new HashMap<String, Object>(); sqlMap.put("sql", sb.toString()); sqlMap.put("paramsValues", paramValues.toArray()); sqlMap.put("paramsTypes", paramsType.toArray()); return sqlMap; } /** * 自动生成插入指定对象的SQL语句,包括对象属性的值为空的字段,不包括自增长主键,若不存在,调用时直接置为null. * @param obj 待生成插入SQL语句的对象 * @param tableName 待插入语句对应的数据库表的名称 * @param keyColumn 数据表主键名称 * @return 返回一个包含SQL语句、SQL语句参数值及参数值类型的Map对象 * @throws IllegalAccessException * @throws IllegalArgumentException */ public static Map<String, Object> generateInsertWithNull(Object obj, String tableName, String keyColumn) throws IllegalArgumentException, IllegalAccessException { StringBuffer columnStrBuf = new StringBuffer(); StringBuffer paramStrBuf = new StringBuffer(); // 记录SQL语句对应插入的占位符 List<Object> columnNameList = new ArrayList<Object>(); // 记录数据表的字段名称 List<Object> paramValues = new ArrayList<Object>(); // 记录对象参数值 List<Integer> paramsType = new ArrayList<Integer>(); // 记录参数值类型 Field[] fields = obj.getClass().getDeclaredFields(); for(int i = 0; i < fields.length; i++){ fields[i].setAccessible(true); // 记录对象属性名称 if(!fields[i].getName().equalsIgnoreCase(keyColumn)){ // 非主键列记录插入SQL语句的参数占位符 columnStrBuf.append(fields[i].getName()); columnNameList.add(fields[i].getName()); if (i != fields.length - 1) { columnStrBuf.append(","); } if("class java.util.Date".equals(fields[i].getType().toString()) && fields[i].get(obj) != null){ String timeStr = DateUtil.formatDate((Date)fields[i].get(obj), "yyyy-MM-dd HH:mm:ss"); paramStrBuf.append("to_date(?, 'yyyy-MM-dd HH24:mi:ss')"); paramValues.add(timeStr); // 记录对象属性的数据类型 paramsType.add(getOrclDataType(fields[i].getType().toString())); }else{ paramStrBuf.append("?"); paramValues.add(fields[i].get(obj)); // 记录对象属性的数据类型 paramsType.add(getOrclDataType(fields[i].getType().toString())); } if (i != fields.length - 1) { paramStrBuf.append(","); } } } // 生成插入操作的SQL语句 StringBuffer sb = new StringBuffer(); sb.append("insert into "); sb.append(tableName); sb.append(" ("); sb.append(columnStrBuf); sb.append(") "); sb.append("values"); sb.append(" ("); sb.append(paramStrBuf); sb.append(")"); // 将生成的SQL语句、SQL语句的列名称用map保存并返回 Map<String, Object> sqlMap = new HashMap<String, Object>(); /* System.out.println(sb.toString()); System.out.println(columnNameList.toString()); System.out.println(paramValues.toString()); System.out.println(paramsType.toString()); */ sqlMap.put("sql", sb.toString()); sqlMap.put("columnNameList", columnNameList.toArray()); sqlMap.put("paramsValues", paramValues.toArray()); sqlMap.put("paramsTypes", paramsType.toArray()); return sqlMap; } /** * 自动生成更新指定对象的SQL语句 * @param obj 待生成更新SQL语句的对象 * @param tableName 待更新语句对应的数据库表的名称 * @param keyColumn 待更新记录的限定字段 * @return 返回一个包含SQL语句及参数值的数组 */ public static Object[] generateUpdate(Object obj, String tableName, String keyColumn) { StringBuffer columnSB = new StringBuffer(); List<Object> params = new ArrayList<Object>(); Object keyValue = http://www.mamicode.com/null;> (6)扩展JAVA对象的反射工具类package com.jkitn.jkits.util; import java.lang.reflect.Field; import java.util.ArrayList; import java.util.List; /** * 说明:扩展JAVA对象的反射机制 * @author xdweleven * @version 1.0 */ public class ReflectionUtil { /** * 设置对象的指定属性名称的属性值 * @param obj 待设置的对象 * @param fieldName 对象属性名称 * @param value 属性值 */ public static void setFieldValue(Object obj, String fieldName, Object value) { Class<? extends Object> c = obj.getClass(); try { Field field = null; Field[] fields = c.getDeclaredFields(); for(int i = 0; i < fields.length; i++){ String fieldNameTemp = fields[i].getName(); if(fieldNameTemp.equalsIgnoreCase(fieldName)){ field = c.getDeclaredField(fieldNameTemp); field.setAccessible(true); field.set(obj, value); return; } } } catch (Exception e) { throw new RuntimeException(e); } } /** * 获取对象的指定属性名称的属性值 * @param obj 待设置的对象 * @param fieldName 对象属性名称 */ public static Object getFieldValue(Object obj, String fieldName) { Class<? extends Object> c = obj.getClass(); Field[] fields = c.getDeclaredFields(); try { for(int i = 0; i < fields.length; i++){ if(fields[i].getName().equalsIgnoreCase(fieldName)){ fields[i].setAccessible(true); return fields[i].get(obj); } } } catch (Exception e) { throw new RuntimeException(e); } return null; } /** * 获取对象的属性值不为空的属性名称 * @param obj 待获取的对象 * @return 返回属性值不为空的对象的属性名称列表 */ public static List<Object> getNotNullField(Object obj) { Class<? extends Object> c = obj.getClass(); List<Object> list = new ArrayList<Object>(); try { Field[] fields = c.getDeclaredFields(); for(int i = 0; i < fields.length; i++){ fields[i].setAccessible(true); if(fields[i].get(obj) != null){ list.add(fields[i]); } } } catch (Exception e) { throw new RuntimeException(e); } return list; } }(7)定义实现数据库的CRUD基本操作BaseDao
package com.jkitn.jkits.dao.common; import java.io.Serializable; import java.sql.ResultSet; import java.util.List; import java.util.Map; import com.jkitn.jkits.common.DBConn; import com.jkitn.jkits.common.PageBean; import com.jkitn.jkits.util.ReflectionUtil; import com.jkitn.jkits.util.SQLUtil; /** * 说明:封装实现数据库的CRUD相关的底层操作。 * @author xdweleven * @version 1.0 */ public class BaseDao{ // 获取数据库链接实例 private DBConn dbconn = new DBConn(); /** 数据库表的前缀 */ public static final String TB_PREFIX = "tb_jkit_"; /** 数据库表的查询前缀 */ public static final String SELECT_TB_PREFIX = "select * from tb_jkit_"; /** 升序排列 */ public static final String ASC = "asc"; /** 降序排列 */ public static final String DESC = "desc"; /** * 根据ID查找对象 * @param classType 对象类型 * @param columnName 编号字段名称 * @param id 对象编号 * @return 返回实体对象 */ public <T> T queryById(Class<T> classType, String columnName, Serializable id) throws Exception{ StringBuffer sqlBuffer = new StringBuffer(); sqlBuffer.append(SELECT_TB_PREFIX); sqlBuffer.append(this.toLowerCaseFirstOne(classType.getSimpleName())); sqlBuffer.append(" where "); sqlBuffer.append(columnName); sqlBuffer.append(" = ? "); this.showSQL(sqlBuffer.toString()); return dbconn.execQuery(sqlBuffer.toString(), new RowMapper<T>(classType), new Object[]{id}).get(0); } /** * 查询所有指定class类型的对象信息 * @param classType 对象类型 * @return 返回实体对象列表 */ public <T> List<T> queryAll(Class<T> classType) throws Exception{ String sql = SELECT_TB_PREFIX + this.toLowerCaseFirstOne(classType.getSimpleName()); this.showSQL(sql); return dbconn.execQuery(sql, new RowMapper<T>(classType)); } /** * 查询指定对象类型的对象信息,并按照指定的排序字段进行升序或降序排序 * @param classType 对象类型 * @param orderColumn 排序字段 * @param ascOrDesc 降序或升序:asc表示升序,desc表示降序 * @return 返回实体对象列表 */ public <T> List<T> queryAllWithOrder(Class<T> classType, String orderColumn, String ascOrDesc) throws Exception{ StringBuffer sqlBuffer = new StringBuffer(); sqlBuffer.append(SELECT_TB_PREFIX); sqlBuffer.append(this.toLowerCaseFirstOne(classType.getSimpleName())); sqlBuffer.append(" order by "); sqlBuffer.append(orderColumn); sqlBuffer.append(" "); sqlBuffer.append(ascOrDesc); this.showSQL(sqlBuffer.toString()); return dbconn.execQuery(sqlBuffer.toString(), new RowMapper<T>(classType)); } /** * 查询指定SQL语句的对象信息列表 * @param sql 查询语句 * @param classType 对象类型 * @param params SQL语句参数 * @return 返回实体对象列表 */ public <T> List<T> query(String sql, Class<T> classType, Object... params) throws Exception{ this.showSQL(sql); return dbconn.execQuery(sql, new RowMapper<T>(classType), params); } /** * 查询指定SQL语句的对象信息列表 * @param sql 查询语句 * @param classType 对象类型 * @param params SQL语句参数 * @return 返回实体对象列表 */ public <T> T queryForObj(String sql, Class<T> classType, Object... params) throws Exception{ this.showSQL(sql); return dbconn.execQuery(sql, new RowMapper<T>(classType), params).get(0); } /** * 分页查询实体对象列表信息 * @param sql 原始的SQL语句 * @param classType 对象类型 * @param curPage 当前页码 * @param rowsPerPage 每页显示的记录数 * @param params SQL语句参数 * @return 返回当前页码的分页对象 */ public <T> PageBean<T> queryByPage(String sql, Class<T> classType, int curPage, int rowsPerPage, Object... params) throws Exception{ // 获取记录总数 int totalRows = this.getTotalRows(sql, params); PageBean<T> pageBean = new PageBean<T>(); pageBean.setCurPage(curPage); // 设置当前页码 pageBean.initPageBean(totalRows, rowsPerPage); // 初始化分页对象的相关属性 // 生成当前分页查询语句(MySql) String pageSql = pageBean.getPageMySQL(sql, curPage, rowsPerPage); this.showSQL(pageSql); // 执行查询操作 pageBean.setPageList(dbconn.execQuery(sql, new RowMapper<T>(classType), params)); return pageBean; } /** * 保存对象到数据库中,若数据库中的用户表有自增序列,则需要指出表中自增列的字段名称,另外, * 数据库中相应的自增序列的名称需按如下格式取名:class名称_自增列字段名称_SEQ, * 例如用户的class为Users,自增序列字段名称为id,则数据库中的自增序列的名称取名为USERS_ID_SEQ. * @param obj 实体对象 * @param sequenceKeyColumn 数据表自增序列的字段名称,若不存在,则置为null。 * @return 返回被更新的记录数 * @throws IllegalAccessException * @throws IllegalArgumentException */ public <T> int insert(T obj, String sequenceKeyColumn) throws Exception{ String tableName = TB_PREFIX + this.toLowerCaseFirstOne(obj.getClass().getSimpleName()); // 自动生成对象的无自增序列插入SQL语句及其相关插入的参数值和类型 Map<String, Object> sqlMap = SQLUtil.generateInsertWithNull(obj, tableName, sequenceKeyColumn); String sql = sqlMap.get("sql").toString(); // SQL语句 Object[] paramsValues = (Object[])sqlMap.get("paramsValues"); // SQL语句的参数值 // int[] paramsTypes = this.parseArrayToInt((Object[])sqlMap.get("paramsTypes")); // 参数值类型 this.showSQL(sql); return dbconn.execUpdate(sql, paramsValues); } /** * 更新对象 * @param obj 待更新的实体对象 * @param keyColumn 更新对象的限定条件字段名称 * @return 返回被更新的记录数 */ public <T> int update(T obj, String keyColumn) throws Exception{ String tableName = TB_PREFIX + this.toLowerCaseFirstOne(obj.getClass().getSimpleName()); // 自动生成对象的更新操作的SQL语句及其参数值 Object[] updateSql = SQLUtil.generateUpdate(obj, tableName, keyColumn); this.showSQL(updateSql[0].toString()); return dbconn.execUpdate(updateSql[0].toString(), (Object[])updateSql[1]); } /** * 删除对象 * @param obj 待更新的实体对象 * @param keyColumn 删除的限定条件字段 * @return 返回被删除的记录数 */ public <T> int delete(T obj, String keyColumn) throws Exception{ // 获取限定条件的值 Object keyValue = http://www.mamicode.com/ReflectionUtil.getFieldValue(obj, keyColumn);>以上是根据自己的理解封装的一些常用DAO层操作,并已运用到部署上线的两个项目中,目前尚未报出BUG。但在效率方面,个人觉得还有很多地方需要优化的地方,比如说批量增删改操作,根据我个人PC机测试的结果,使用JDBC自带的批量操作,要比上述简单循环的操作速度要慢,但我还是觉得这部分的效率还有很大的提升空间,希望能有位IT大卡给我指点一二,不甚感激!