考虑到项目将在全省各市部署,预想到在条件允许的市需要调其他市的档案,因此希望DAO能针对接口编程,方便以后扩展以及适应不同的市。
于是在SpringSide的examples.miniweb上作了些修改,希望能够抛砖引玉,更希望各位批评指正。
将IdEntity改为EntityObject,希望以后所有实体都继承它。Id不一定放在这,但要重写equals和hashCode等。
package org.springside.examples.miniweb.entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.MappedSuperclass;
import org.apache.commons.lang.builder.EqualsBuilder;
import org.apache.commons.lang.builder.HashCodeBuilder;
import org.apache.commons.lang.builder.ToStringBuilder;
import org.apache.commons.lang.builder.ToStringStyle;
/**
* 统一定义id的entity基类.
*
* 基类统一定义id的属性名称、数据类型、列名映射及生成策略.
* 子类可重载getId()函数重定义id的列名映射和生成策略.
*
* @author calvin
*/
//JPA 基类的标识
@MappedSuperclass
public abstract class EntityObject {
protected Long id;
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
//@GeneratedValue(strategy = GenerationType.SEQUENCE)
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
@Override
public String toString() {
return ToStringBuilder.reflectionToString(this,
ToStringStyle.MULTI_LINE_STYLE);
}
@Override
public boolean equals(Object o) {
return o != null && o.getClass().equals(this.getClass())
&& EqualsBuilder.reflectionEquals(this, o);
}
@Override
public int hashCode() {
return HashCodeBuilder.reflectionHashCode(this);
}
}
定义DAO的通用接口,如下,还有很多常用通用的没有写出来。
package org.springside.examples.miniweb.dao;
import java.io.Serializable;
import java.util.Collection;
import java.util.List;
import org.springside.examples.miniweb.entity.EntityObject;
/**
* 通用DAO接口<br>
* 在能用此接口完成功能时,不必再派生出子接口<br>
* 在此接口不能满足要求时,派生出相应的子接口<br>
*
* @see org.springside.examples.miniweb.dao.security.RoleDao
* @see org.springside.examples.miniweb.dao.security.ResourceDao
*
* @author langshao
*
*/
public interface GenericDao {
/**
* 根据实体的class及id获取相应的实体
*
* @param <T>
* @param entityClass 实体的class, 如 <code>User.class</code>
* @param id 当为 <code>null</code> 时返回 <code>null</code>
* @return 找到则返回相应的实体, 找不到时返回 <code>null</code>
*/
<T extends EntityObject> T get(Class<T> entityClass, Serializable id);
/**
* 根据实体的class获取所有的实体
*
* @param <T>
* @param entityClass 实体的class, 如 <code>User.class</code>
* @return 没有数据时返回空的 List
*/
<T extends EntityObject> List<T> getAll(Class<T> entityClass);
/**
* 新增或更新实体<br>
* 当entity的id为 <code>null</code> 时新增, 否则更新
*
* @param entity 当为 <code>null</code> 时不保存
*/
void saveOrUpdate(EntityObject entity);
/**
* 批量新增或更新实体<br>
* 当entity的id为 <code>null</code> 时新增, 否则更新
*
* @param entities 当entities为 <code>null</code> 时不保存
* @exception IllegalArgumentException entities的成员含有 <code>null</code>
* 时会抛出异常
*/
void saveOrUpdateAll(Collection<? extends EntityObject> entities)
throws IllegalArgumentException;
/**
* 删除实体
*
* @param entity 当为 <code>null</code> 时不删除
*/
void delete(EntityObject entity);
/**
* 批量删除实体
*
* @param entities 当entities为 <code>null</code> 时不删除
* @exception IllegalArgumentException entities的成员含有 <code>null</code>
* 时会抛出异常
*/
void deleteAll(Collection<? extends EntityObject> entities)
throws IllegalArgumentException;
}
一个简单的实现类如下,我是比较喜欢用HibernateTemplate。
package org.springside.examples.miniweb.dao.impl;
import java.io.Serializable;
import java.util.Collection;
import java.util.List;
import org.hibernate.SessionFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.orm.hibernate3.support.HibernateDaoSupport;
import org.springframework.stereotype.Repository;
import org.springside.examples.miniweb.dao.GenericDao;
import org.springside.examples.miniweb.entity.EntityObject;
/**
* 通用DAO实现
*
* @author langshao
*
*/
@Repository
public class GenericDaoImpl extends HibernateDaoSupport implements GenericDao {
/**
* 为了使sessionFactory能自动注入而写了这个方法
*
* @param sessionFactory
*/
@Autowired
public void setSessionFactory4Autowired(SessionFactory sessionFactory) {
this.setSessionFactory(sessionFactory);
}
@SuppressWarnings("unchecked")
public <T extends EntityObject> T get(Class<T> entityClass, Serializable id) {
if (id != null)
return (T) getHibernateTemplate().get(entityClass, id);
return null;
}
@SuppressWarnings("unchecked")
public <T extends EntityObject> List<T> getAll(Class<T> entityClass) {
return getHibernateTemplate().loadAll(entityClass);
}
public void saveOrUpdate(EntityObject entity) {
if (entity != null) {
getHibernateTemplate().saveOrUpdate(entity);
}
}
public void saveOrUpdateAll(Collection<? extends EntityObject> entities)
throws IllegalArgumentException {
if (entities != null && !entities.isEmpty()) {
getHibernateTemplate().saveOrUpdateAll(entities);
}
}
public void delete(EntityObject entity) {
if (entity != null) {
getHibernateTemplate().delete(entity);
}
}
public void deleteAll(Collection<? extends EntityObject> entities)
throws IllegalArgumentException {
if (entities != null) {
getHibernateTemplate().deleteAll(entities);
}
}
}
在删除角色时,需要将相应的关联表也删除,因此角色需要重写delete的实现,为了方便自动注入,建立一个新的接口:
package org.springside.examples.miniweb.dao.security;
import org.springside.examples.miniweb.dao.GenericDao;
/**
* 角色DAO接口
*
* @author langshao
*/
public interface RoleDao extends GenericDao {
}
实现如下:
package org.springside.examples.miniweb.dao.security.impl;
import java.util.List;
import org.springframework.stereotype.Repository;
import org.springside.examples.miniweb.dao.impl.GenericDaoImpl;
import org.springside.examples.miniweb.dao.security.RoleDao;
import org.springside.examples.miniweb.entity.EntityObject;
import org.springside.examples.miniweb.entity.security.User;
/**
* 角色DAO实现
*
* @author langshao
*
*/
@Repository
public class RoleDaoImpl extends GenericDaoImpl implements RoleDao {
private static final String QUERY_USER_BY_ROLEID = "select u from User u left join u.roleList r where r.id = ?";
/**
* 重载函数,因为Role中没有建立与User的关联,因此需要以较低效率的方式进行删除User与Role的多对多中间表.
*/
@Override
@SuppressWarnings("unchecked")
public void delete(EntityObject role) {
// 查询出拥有该角色的用户,并删除该用户的角色.
List<User> users = getHibernateTemplate().find(QUERY_USER_BY_ROLEID,
role.getId());
for (User u : users) {
u.getRoleList().remove(role);
}
super.delete(role);
}
}
这样做之后,GenericDao 的 @Autowired 便会报错,以 RoleDaoTest 来说明如何解决之。
在applicationContext-test.xml中加入一个bean:
<bean id="genericDao" class="org.springside.examples.miniweb.dao.impl.GenericDaoImpl" />
然后RoleDaoTest如下写:
package org.springside.examples.miniweb.integration.dao.security;
import javax.annotation.Resource;
import org.junit.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springside.modules.test.DataUtils;
import org.springside.modules.test.spring.SpringTxTestCase;
import org.springside.examples.miniweb.dao.GenericDao;
import org.springside.examples.miniweb.dao.security.RoleDao;
import org.springside.examples.miniweb.entity.security.Role;
import org.springside.examples.miniweb.entity.security.User;
/**
* UserDao的集成测试用例,测试ORM映射及特殊的DAO操作.
*
* @author calvin
*/
public class RoleDaoTest extends SpringTxTestCase {
@Autowired
private RoleDao roleDao;
@Resource(name = "genericDao")
private GenericDao userDao;
/**
* 测试删除角色时删除用户-角色的中间表.
*/
@Test
public void deleteRole() {
//新增测试角色并与admin用户绑定.
Role role = new Role();
role.setName(DataUtils.randomName("Role"));
roleDao.saveOrUpdate(role);
User user = userDao.get(User.class, 1L);
user.getRoleList().add(role);
userDao.saveOrUpdate(user);
flush();
int oldJoinTableCount = countRowsInTable("SS_USER_ROLE");
int oldUserTableCount = countRowsInTable("SS_USER");
//删除用户角色, 中间表将减少1条记录,而用户表应该不受影响.
roleDao.delete(role);
flush();
int newJoinTableCount = countRowsInTable("SS_USER_ROLE");
int newUserTableCount = countRowsInTable("SS_USER");
assertEquals(1, oldJoinTableCount - newJoinTableCount);
assertEquals(0, oldUserTableCount - newUserTableCount);
}
}
再举个扩充通用接口的例子:
package org.springside.examples.miniweb.dao.security;
import java.util.List;
import org.springside.examples.miniweb.dao.GenericDao;
import org.springside.examples.miniweb.entity.security.Resource;
/**
* 受保护资源对象的DAO接口
*
* @author langshao
*/
public interface ResourceDao extends GenericDao {
/**
* 查询URL类型的资源并预加载可访问该资源的授权信息.
*/
List<Resource> getUrlResourceWithAuthorities();
}
实现如下:
package org.springside.examples.miniweb.dao.security.impl;
import java.util.List;
import org.springframework.stereotype.Repository;
import org.springside.examples.miniweb.dao.impl.GenericDaoImpl;
import org.springside.examples.miniweb.dao.security.ResourceDao;
import org.springside.examples.miniweb.entity.security.Resource;
/**
* 受保护资源对象的DAO实现
*
* @author langshao
*/
@Repository
public class ResourceDaoImpl extends GenericDaoImpl implements ResourceDao {
public static final String QUERY_BY_RESOURCETYPE_WITH_AUTHORITY = "select distinct r from Resource r "
+ "left join fetch r.authorityList WHERE r.resourceType=? ORDER BY r.position ASC";
@SuppressWarnings("unchecked")
public List<Resource> getUrlResourceWithAuthorities() {
return getHibernateTemplate().find(
QUERY_BY_RESOURCETYPE_WITH_AUTHORITY,
Resource.URL_TYPE);
}
}
这样做主要是想针对接口编程,还有就是不想每新增一个Entity就要增加一个DAO,只要通用的够用就不用再写了。
Write less, do more.