后台的结构图如下:
这里是模仿了SSH的组织方式,因为毕竟大部分是学SSH过来的(ME也是其中之一),变化太大可能会有理解上的困难。
这里的Dao层被去掉了,因为Nutz本身提供的NutzDao就提供了基本的增删改查操作,因此这层可以去掉了,直接并入到Service层中。
接下来,先写model层,就是对于数据库表的JavaBean。
详细步骤请参照Nutz 的文档Dao手册这部分:http://code.google.com/p/nutz/wiki/dao_hello
以User举例,其他模块类似
User类:
package org.nutz.demo.model; import java.util.List; import org.nutz.dao.entity.annotation.Column; import org.nutz.dao.entity.annotation.Many; import org.nutz.dao.entity.annotation.Table; /** * 用户。 * * @author [email protected] * */ @Table("t_user") public class User extends Identity { @Column() private String userName; @Column() private String password; @Column() private String userType; @Many(target = ContactType.class, field = "userId") private List<ContactType> contactTypes; @Many(target = Contact.class, field = "userId") private List<Contact> contacts; @Many(target = Blog.class, field = "userId") private List<Blog> blogs; // 这里省略了get与set }
要注意的是,不要忘了加Nutz的注释,同时如果字段与表中列名称有出入的话,要写入@Column("表列名")中,替换掉默认值。
下面开始写Service层共通的类,所有的Service都要继承这个基类,就可以实现增删改查的操作了。
这里对于增删改三项(查询操作的返回值就是查询的结果集,没有封装的必要)操作的返回值,做了一个简单的封装,其中包含了一部分业务信息,用一个枚举类型代替默认的返回值。
DbOperationResultEnum :
package org.nutz.demo.util.database; /** * 封装了各种数据库操作结果。 * * @author [email protected] * */ public enum DbOperationResultEnum { OPERATION_SUCCESS(true, "操作成功。"), OPERATION_FAILURE(false, "操作失败。"), INSERT_SUCCESS(true, "插入成功。"), INSERT_FAILURE(false, "插入失败。"), UPDATE_SUCCESS(true, "更新成功。"), UPDATE_FAILURE(false, "更新失败。"), DELETE_SUCCESS(true, "删除成功。"), DELETE_FAILURE(false, "删除失败,数据可能被引用,请先删引用关系。"), CLEAR_SUCCESS(true, "批量删除成功。"), CLEAR_FAILURE(false, "批量删除失败,数据可能被引用,请先删引用关系。"); private boolean success; private String msg; private DbOperationResultEnum(boolean success, String msg) { this.success = success; this.msg = msg; } public boolean isSuccess() { return success; } public String getMsg() { return msg; } }
BaseService:
package org.nutz.demo.service; import java.util.List; import org.nutz.dao.Chain; import org.nutz.dao.Condition; import org.nutz.dao.QueryResult; import org.nutz.dao.sql.Sql; import org.nutz.demo.util.database.DbOperationResultEnum; /** * CRUD基本操作。<br> * 基于泛型类。 * * @author [email protected] * * @param <T> * 实体类类型 */ public interface BaseService<T> { /** * 从配置SQL文件中取得SQL文。 * * @param key * @return */ public Sql createSql(String key); /** * 直接执行一组SQL语句。 * * @param sqls * @return */ public boolean execute(Sql... sqls); /** * 通用查询。(无分页) * * @param cdn * @return */ public List<T> query(Condition cdn); /** * 通用查询。(带分页信息) * * @param cdn * @param pageNumber * @param pageSize * @return */ public QueryResult query(Condition cdn, int pageNumber, int pageSize); /** * 通用获取,对象中引用的对象。(根据正则匹配) * * @param obj * @param regex * @return */ public T fetchLinks(T obj, String regex); /** * 通用获取。(根据条件) * * @param cdn * @return */ public T fetch(Condition cdn); /** * 通用获取。(根据id) * * @param id * @return */ public T fetch(long id); /** * 通用获取。(根据name) * * @param name * @return */ public T fetch(String name); /** * 通用删除。(根据实体) * * @param entity * @return */ public DbOperationResultEnum delete(T entity); /** * 通用插入。(根据实体) * * @param entity * @return */ public DbOperationResultEnum insert(T entity); /** * 通用更新。(根据条件) * * @param entity * @return */ public DbOperationResultEnum update(Condition cnd, Chain chain); /** * 通用更新。(根据实体) * * @param entity * @return */ public DbOperationResultEnum update(T entity); /** * 通用批量删除。(删除全部) * * @return */ public DbOperationResultEnum clear(); /** * 通用批量删除。(根据条件删除) * * @param cdn * @return */ public DbOperationResultEnum clear(Condition cdn); /** * 返回该Service使用的实体类类型。 * * @return */ public Class<T> getEntryClz(); }
其中的方法可以根据你的需求再自行添加。
这里写到后来才发现,BaseService这个基类中的功能与Nutz中org.nutz.service 包下的几个类,EntityService,IdEntityService,NameEntityService提供的功能相似
大家可以根据情况直接使用Nutz提供的,或作为参考,根据自身情况写出更符合自己使用习惯的共通类,来进行复用。
下面是BaseService的实现类:
package org.nutz.demo.service.impl; import java.util.List; import org.nutz.dao.Chain; import org.nutz.dao.Condition; import org.nutz.dao.Dao; import org.nutz.dao.QueryResult; import org.nutz.dao.impl.NutDao; import org.nutz.dao.pager.Pager; import org.nutz.dao.sql.Sql; import org.nutz.demo.exception.BizException; import org.nutz.demo.service.BaseService; import org.nutz.demo.util.DaoUtil; import org.nutz.demo.util.database.DSConfig; import org.nutz.demo.util.database.DbOperationResultEnum; import org.nutz.lang.Mirror; import org.nutz.log.Log; import org.nutz.log.Logs; /** * 通用操作 实现。<br> * * @author [email protected] * */ public abstract class BaseServiceImpl<T> implements BaseService<T> { protected Log logger = Logs.getLog(getClass()); private Mirror<T> mirror; @SuppressWarnings("unchecked") public BaseServiceImpl() { // 尝试获得泛型的类型 try { Class<T> entryClass = (Class<T>) Mirror.getTypeParam(getClass(), 0); mirror = Mirror.me(entryClass); if (logger.isDebugEnabled()) logger.debugf("获得泛型的实际类型: %s", entryClass.getName()); } catch (Throwable e) { if (logger.isWarnEnabled()) logger.warn("!!!无法获得泛型类型!", e); throw new RuntimeException(e); } } /** * 方便的提供上层Dao。 * * @return */ public NutDao getDao() { return DaoUtil.getDao(); } /** * 方便的提供上层Dao。 * * @param dbConfig * @return */ public NutDao getDao(DSConfig dbConfig) { return DaoUtil.getDao(dbConfig); } /** * 获得当前泛型类型。 * * @return */ public Class<T> getEntryClz() { return mirror.getType(); } /** * 从配置SQL文件中取得SQL文。 * * @param key * @return */ public Sql createSql(String key) { return DaoUtil.getSQLDao().sqls().create(key); } /** * 直接执行一组SQL语句。 * * @param sqls */ public boolean execute(Sql... sqls) { try { DaoUtil.getDao().execute(sqls); return true; } catch (Throwable e) { if (logger.isErrorEnabled()) { logger.error("批量执行SQL语句报错。", e); } throw new RuntimeException(e); } } /** * 通用查询。(无分页) * * @param cdn * @return */ public List<T> query(Condition cdn) { return DaoUtil.getDao().query(getEntryClz(), cdn, null); } /** * 通用查询。(带分页信息) * * @param cdn * @param pageNumber * @param pageSize * @return */ public QueryResult query(Condition cdn, int pageNumber, int pageSize) { Dao dao = DaoUtil.getDao(); Pager pager = dao.createPager(pageNumber, pageSize); List<T> list = dao.query(getEntryClz(), cdn, pager); if (null != pager) { pager.setRecordCount(dao.count(getEntryClz(), cdn)); } return new QueryResult(list, pager); } /** * 通用获取,对象中引用的对象。(根据正则匹配) * * @param obj * @param regex * @return */ public T fetchLinks(T obj, String regex) { return DaoUtil.getDao().fetchLinks(obj, regex); } /** * 通用获取。(根据条件) * * @param cdn * @return */ public T fetch(Condition cdn) { return DaoUtil.getDao().fetch(getEntryClz(), cdn); } /** * 通用获取。(根据id) * * @param id * @return */ public T fetch(long id) { return DaoUtil.getDao().fetch(getEntryClz(), id); } /** * 通用获取。(根据name) * * @param name * @return */ public T fetch(String name) { return DaoUtil.getDao().fetch(getEntryClz(), name); } /** * 通用删除。(根据实体) * * @param entity * @return */ public DbOperationResultEnum delete(T entity) { try { return 1 == DaoUtil.getDao().delete(entity) ? DbOperationResultEnum.DELETE_SUCCESS : DbOperationResultEnum.DELETE_FAILURE; } catch (Throwable e) { if (logger.isErrorEnabled()) { logger.error("删除数据出错。", e); } throw new BizException(DbOperationResultEnum.DELETE_FAILURE, e); } } /** * 通用插入。(根据实体) * * @param entity * @return */ public DbOperationResultEnum insert(T entity) { try { DaoUtil.getDao().insert(entity); return DbOperationResultEnum.INSERT_SUCCESS; } catch (Throwable e) { if (logger.isErrorEnabled()) { logger.error("插入数据出错。", e); } throw new BizException(DbOperationResultEnum.INSERT_FAILURE, e); } } /** * 通用更新。(根据条件) * * @param entity * @return */ public DbOperationResultEnum update(Condition cnd, Chain chain) { try { return DaoUtil.getDao().update(getEntryClz(), chain, cnd) >= 0 ? DbOperationResultEnum.UPDATE_SUCCESS : DbOperationResultEnum.UPDATE_FAILURE; } catch (Throwable e) { if (logger.isErrorEnabled()) { logger.error("更新数据出错。", e); } throw new BizException(DbOperationResultEnum.UPDATE_FAILURE, e); } } /** * 通用更新。(根据实体) * * @param entity * @return */ public DbOperationResultEnum update(T entity) { try { return 1 == DaoUtil.getDao().update(entity) ? DbOperationResultEnum.UPDATE_SUCCESS : DbOperationResultEnum.UPDATE_FAILURE; } catch (Throwable e) { if (logger.isErrorEnabled()) { logger.error("更新数据出错。", e); } throw new BizException(DbOperationResultEnum.UPDATE_FAILURE, e); } } /** * 通用批量删除。(删除全部) * * @return */ public DbOperationResultEnum clear() { return clear(null); } /** * 通用批量删除。(根据条件删除) * * @param cdn * @return */ public DbOperationResultEnum clear(Condition cdn) { try { return DaoUtil.getDao().clear(getEntryClz(), cdn) >= 0 ? DbOperationResultEnum.CLEAR_SUCCESS : DbOperationResultEnum.CLEAR_FAILURE; } catch (Throwable e) { if (logger.isErrorEnabled()) { logger.error("批量删除数据出错。", e); } throw new BizException(DbOperationResultEnum.CLEAR_FAILURE, e); } } }
其中导入的几个新类要简单介绍下:
DaoUtil类:
package org.nutz.demo.util; import org.nutz.dao.impl.FileSqlManager; import org.nutz.dao.impl.NutDao; import org.nutz.demo.util.database.DSConfig; import org.nutz.demo.util.database.DSContainer; import org.nutz.demo.util.database.DSUtil; import org.nutz.log.Log; import org.nutz.log.Logs; /** * DAO层切换类。 * * @author [email protected] * */ public abstract class DaoUtil { protected static Log logger = Logs.getLog(DaoUtil.class); private static NutDao sqlDao = new NutDao(); private static NutDao defaultDao = new NutDao(DSContainer.getDataSource(DSUtil .getDefaultDSConfig())); /** * 加载配置SQL */ static { sqlDao.setSqlManager(new FileSqlManager("/config/sql")); if (logger.isDebugEnabled()) { logger.debugf("加载SQL配置文件成功,共%s条", sqlDao.sqls().count()); for (String key : sqlDao.sqls().keys()) { String sql = sqlDao.sqls().get(key); logger.debugf("Key: %s Value: %s", key, sql); } } } public static NutDao getSQLDao() { return sqlDao; } /** * 获得当前选中的Dao。 * * @return */ public static NutDao getDao() { return defaultDao; } /** * 根据数据源配置信息,获得Dao。 * * @param dbConfig * @return */ public static NutDao getDao(DSConfig dbConfig) { return new NutDao(DSContainer.getDataSource(dbConfig)); } }
这个类负责返回普通的NutzDao,跟一个用来获得自定义SQL语句的特殊的NutzDao。
这里涉及到这个包下的几个类:
大家可能很奇怪为什么这里代码要这么多,这里其实从现有项目中搬过来的,是因为在做的项目中有这样一个需求——适应多数据源。
这个适应不只是在配置文件的时候要可以使用不同的数据源,在运行过程中,也要可以随时加入新的数据源,一个Service的每次执行都可以指定使用不同的数据源。
当然这里的系统不会有这么复杂的情况,代码的话大家可以选择性的看看,后面有时间了ME会把这部分精简掉。
BizException类:
package org.nutz.demo.exception; import org.nutz.demo.util.database.constant.DbOperationResultEnum; /** * 一个包含异常信息跟数据库操作结果信息的异常类。 * * @author [email protected] * */ @SuppressWarnings("serial") public class BizException extends RuntimeException { private DbOperationResultEnum dbOperationResultEnum = null; private Throwable cause = null; /** * 业务异常。 * * @param dbOperationResultEnum * @param cause */ public BizException(DbOperationResultEnum dbOperationResultEnum, Throwable cause) { super(cause); this.cause = cause; this.dbOperationResultEnum = dbOperationResultEnum; } public Throwable getCause() { return this.cause; } public DbOperationResultEnum getDBResult() { return this.dbOperationResultEnum; } }
包含异常信息跟数据库操作结果信息的异常类,这里是为了保证在数据库出错的时候,仍然能正常的在页面上返回合理的结果,并将这个异常记录下来,这个类会跟后面的AOP配合使用,对其进行拦截,并将其异常写入日志,其结果返回页面。
好了,接下来开始实现四个模块的Service
这里以User为例子,展示Service接口与实现如何编写。
接口:
package org.nutz.demo.service; import org.nutz.demo.model.User; /** * 用户模块Service接口。 * * @author [email protected] * */ public interface UserService extends BaseService<User> { }
实现:
package org.nutz.demo.service.impl; import org.nutz.demo.model.User; import org.nutz.demo.service.UserService; /** * 用户模块Service实现类。 * * @author [email protected] * */ public class UserServiceImpl extends BaseServiceImpl<User> implements UserService { }
是不是很简单,就这样,基本的增删改查功能就有了,那么接口与实现还存在的意义,就是在其中加入某个模块所具有的特殊的业务逻辑,而基本CRUD操作实现,你可以一行代码都不用写了,都在基类中实现好了。
最终代码如下:
下面把数据库配置文件加入,然后写几个测试类,看看刚才写的Service是否成功。
数据库配置文件ME这里采用了过去常用的properties文件,因为很多人还不是太熟悉用json格式(好吧,其实当时是ME忘了用json了)
放在这里,同时这里可以放一个log4j的配置文件,反正早晚都需要。
下面是测试类,还是以User为例,其他类似。
package org.nutz.demo.service; import java.util.List; import org.junit.After; import org.junit.AfterClass; import org.junit.Assert; import org.junit.Before; import org.junit.BeforeClass; import org.junit.Test; import org.nutz.dao.Cnd; import org.nutz.demo.model.User; import org.nutz.demo.service.impl.UserServiceImpl; import org.nutz.demo.util.database.constant.DbOperationResultEnum; public class UserServiceTest { private static UserService userService; // 测试数据 private final static String USERNAME1 = "testUser1"; private final static String USERNAME2 = "testUser2"; private final static String USERNAME3 = "testUser3"; private final static String USERNAME4 = "testUser4"; private final static String USERNAME4_2 = "testUser4_2"; private final static String PASSWORD = "test123"; private final static String ADMIN = "管理员"; private final static String USER = "普通用户"; @BeforeClass public static void beforeClass() { userService = new UserServiceImpl(); // 准备测试数据 User user1 = new User(); user1.setUserName(USERNAME1); user1.setPassword(PASSWORD); user1.setUserType(ADMIN); User user2 = new User(); user2.setUserName(USERNAME2); user2.setPassword(PASSWORD); user2.setUserType(USER); User user3 = new User(); user3.setUserName(USERNAME3); user3.setPassword(PASSWORD); user3.setUserType(USER); // 清理当前数据 userService.clear(); // 插入测试数据 userService.insert(user1); userService.insert(user2); userService.insert(user3); } @AfterClass public static void afterClass() { // 清理所有测试数据 userService.clear(); } @Before public void beforeMethod() { } @After public void afterMethod() { } @Test public void insert() throws Exception { User user = new User(); user.setUserName(USERNAME4); user.setPassword(PASSWORD); user.setUserType(ADMIN); DbOperationResultEnum result = userService.insert(user); Assert.assertTrue(result.isSuccess()); } @Test public void fetch() throws Exception { User user = userService.fetch(Cnd.where("userName", "=", USERNAME1)); Assert.assertNotNull(user); Assert.assertTrue("管理员".equals(user.getUserType())); } @Test public void update() throws Exception { User user = userService.fetch(Cnd.where("userName", "=", USERNAME4)); user.setUserName(USERNAME4_2); user.setUserType(USER); DbOperationResultEnum result = userService.update(user); Assert.assertTrue(result.isSuccess()); } @Test public void query() throws Exception { List<User> users1 = userService.query(Cnd.where("userName", "=", USERNAME2).and("userType", "=", USER)); List<User> users2 = userService.query(null); Assert.assertTrue(users1.size() == 1); Assert.assertTrue(users2.size() == 4); } @Test public void delete() throws Exception { User user = new User(); user.setId(1000000); DbOperationResultEnum result = userService.delete(user); Assert.assertFalse(result.isSuccess()); } @Test public void clear() throws Exception { DbOperationResultEnum result = userService.clear(Cnd.where("userName", "=", USERNAME4_2)); Assert.assertTrue(result.isSuccess()); } }
确认无误后,说明后台的增删改查基本功能已经没有问题,可以开始进行下面的编码了。
下一集,将讲述MVC与IoC的使用。