后台的结构图如下:
这里是模仿了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 pangwu86@gmail.com
*
*/
@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 pangwu86@gmail.com
*
*/
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 pangwu86@gmail.com
*
* @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 pangwu86@gmail.com
*
*/
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 pangwu86@gmail.com
*
*/
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 pangwu86@gmail.com
*
*/
@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 pangwu86@gmail.com
*
*/
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 pangwu86@gmail.com
*
*/
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的使用。