论坛首页 Java企业应用论坛

Rapae 弱化DAO的一种方法

浏览 12144 次
精华帖 (0) :: 良好帖 (19) :: 新手帖 (0) :: 隐藏帖 (0)
作者 正文
   发表时间:2008-06-15   最后修改:2010-06-23
DAO

        可怜的DAO层已经被各位大侠蹂躏得体肤完肤了,从范型DAO一直被蹂躏到现在只剩下一个可怜巴巴的接口,无不体现Java人追求敏捷开发的热情。其实,DAO层本来的作用就应该自从Hibernate一类优秀的ORM框架诞生之日起就应该消失灭迹了的。既然如此,那么我们就毁灭得更彻底一点。

 

下面是我对Service与DAO层整合的一些构想

 

约定优先于配置
一、Rapae代理接口标签定义

query属性:查询语句或者命名查询名称
  1. 不止由一个单词组成时:简单查询语句
  2. 由单个单词组成时:为命名查询,名称规则为[query的值]
  3. 为默认值""时:为命名查询,名称规则为[类名.方法名]
count属性:用于统计个数的查询语句或者命名查询名称,固定返回为一个Long型的数据
  1. 不止由一个单词组成时:简单查询语句
  2. 由单个单词组成时:为民命查询,名称规则为[count的值]
  3. 为默认值""时:自动生成count(*)语句
    • query为语句查询时:count自动生成为语句查询,规则为:select count(*) + query从第一个from开始到语句结束
    • query为命名查询时:count自动生成为命名查询,规则为:[query命名查询名称_count]
PS:当接口的方法没有Rapae注释的时候,按query与count均属于默认值情况处理








二、Pagination翻页注释

注释定义:
  1. FirstResult注释:方法级上的Annotation,它标记了分页查询时所要知道的第一条记录所在的位置。期待的类型为int
  2. MaxSize注释:方法级上的Annotation,它标记了分页查询时所要知道的每页最大查询记录数。期待的类型为int
  3. Total注释:方法级上的Annotation,它标记了分页查询后返回的总数统计。期待的类型为long
  4. Result注释:方法级上的Annotation,它标记了分页查询后返回的结果。期待的类型为java.util.Collection<E>
详细说明:
  1. 分页查询时:第一个参数必须是能同时提供有FirstResult和MaxSize注释方法的类,并且方法期待的返回类型都必须匹配,否则将会抛出异常。Rapae通过调用被标注的方法来进行分页查询。
  2. 分页查询返回时:返 回的类必须同时提供有Result和Total注释的方法,参数个数为1,并且期待的类型都必须匹配,否则将会抛出异常。与此同时,若返回的类同时还能提 供FirstResult、MaxSize注释的方法,方法的参数个数为1且为期待类型,那么Rapae在分页查询完成后将查询用到的 FirstResult、MaxSize值原封不动的通过标注的方法设回给返回类。






三、CRUD基本查询注释

注释定义:
  1. Create注释:格式 T [方法名] (T t);
  2. Read注释:格式 T [方法名] (java.io.Serializable pk);
  3. Update注释:格式 T [方法名] (T t);
  4. Delete注释:格式 T [方法名] (java.io.Serializable pk);
一旦被标注上了CRUD标签,则必须严格遵循标签所规定的格式,否则抛出异常。






四、查询行为方式与方法的返回类型、参数类型、参数个数以及方法名称之间的约定

查询行为方式
  1.  
    1. 执行查询
      • 返回值必须为void
    2. 查询多条记录(不翻页)
      • 返回值是java.util.Collection<E>的实现
    3. 查询多条记录(翻页)
      • 返回类必须同时提供标注有Result与Total的方法,并且参数个数为1,参数类型为标签所期待的类型
      • 返回类可以选择性的提供标注有FirstResult和MaxSize的方法,并且参数个数为1,参数类型为标签所期待的类型
      • 传入的第一个参数必须同时提供标注有FirstResult与MaxSize的方法,参数个数为0,返回类型为标签所期待的类型
    4. 查询单条记录
      • 返回值必须是一个对象
查询条件传递:
  1. 若是翻页查询,则参数个数至少为一个,且第一个参数必须为提供标标注有FirstResult与MaxSize的方法。
  2. 若查询条件是通过可变参或者Collection集合类进行传递的,则按顺序对查询条件进行设置
  3. 若查询条件是通过参数列表直接传递进来的,则按参数列表定义的顺序对查询条件进行设置
  4. 若查询条件是通过Map传递进来的,则可通过Map对参数的参数进行设置
   发表时间:2008-06-15  

首先我们先来看效果:

 

假设我们有这样一个Entity类

import java.io.Serializable;
import java.util.Date;
import javax.persistence.*;

@Entity
@Table(name = "ACCOUNT")
@SequenceGenerator(name = "ACCOUNT_SEQUENCE", sequenceName = "SEQ_ACCOUNT", initialValue = 1, allocationSize = 1)
public class Account implements Serializable {

	private static final long serialVersionUID = 1L;
	
	
	@Id
	@Column(name = "ACCOUNT_ID")
	@GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "ACCOUNT_SEQUENCE")
	private Long id;
	
	@Column(name = "USERNAME",length=20, unique = true, nullable = false)
	private String username;
	
	@Column(name = "PASSWORD",length=20, nullable = false)
	private String password;
	
	@Column(name = "REG_TIME", nullable = false)
	@Temporal(TemporalType.TIMESTAMP)
	private Date registerTime;
	
	Getter/Setter

	public int hashCode() {
		return (this.id == null) ? 0 : this.id.hashCode();
	}

	public boolean equals(Object object) {
		if (object instanceof Account) {
			final Account obj = (Account) object;
			return (this.id != null) ? this.id.equals(obj.id)
					: (obj.id == null);
		}
		return false;
	}

}

 

那么,我们在实际的开发过程中,只用写一个接口就能实现大部分的业务逻辑功能。

import java.util.Collection;

import org.springframework.transaction.annotation.Transactional;

import test.com.rapae.model.Account;

import com.javaforge.rapae.annotation.Rapae;
import com.javaforge.rapae.annotation.crud.*;
import com.javaforge.rapae.util.pagination.Page;
import com.javaforge.rapae.util.pagination.Pagination;

@Transactional
public interface AccountService {

	/**
	 * 注册新帐号
	 * 
	 * @param account
	 * @return
	 */
	@Create
	Account register(Account account);

	/**
	 * 通过ID获取帐号
	 * 
	 * @param id
	 * @return
	 */
	@Read
	Account getById(Long id);

	/**
	 * 帐号修改
	 * 
	 * @param account
	 * @return
	 */
	@Update
	Account update(Account account);

	/**
	 * 删除帐号
	 * 
	 * @param id
	 * @return
	 */
	@Delete
	Account delete(Long id);

	/**
	 * 通过用户名获取帐号
	 * 
	 * @param username
	 * @return
	 */
	@Rapae(query = "from Account where username = ?")
	Account getByUsername(String username);

	/**
	 * 帐号登录
	 * 
	 * @param username
	 * @param password
	 * @return
	 */
	@Rapae(query = "from Account where username = ? and password = ?")
	Account login(String username, String password);
	
	/**
	 * 不翻页获取所有的帐号
	 * @return
	 */
	@Rapae(query="from Account")
	Collection<Account> findAll();
	
	/**
	 * 翻页查询获取所有的帐号
	 * @param username
	 * @return
	 */
	@Rapae(query="from Account")
	Pagination<Account> countPageFindAll(Page page);
	

}

 

然后...没有然后了,接口上都已经写得非常清楚了,剩下的就交给Rapae去代理实现吧。

在Spring中这样配置

	<bean id="rapaeProxyHandler"
		class="com.javaforge.rapae.handler.JPARapaeProxyHandler"
		scope="prototype">
		<property name="entityManagerFactory"
			ref="entityManagerFactory" />
	</bean>

	<bean id="accountService"
		class="com.javaforge.rapae.factory.GenericRapaeFactoryBean">
		<property name="target"
			value="test.com.rapae.service.AccountService" />
		<property name="rapaeProxyHandler" ref="rapaeProxyHandler" />
	</bean>

 JPARapaeProxyHandler类是负责为Rapae实现代理生成JPA调用的执行者

GenericRapaeFactoryBean则是一个实现了Spring的FactoryBean接口的一个工厂类

 

然后我们就可以像正常的Spring中被托管的Bean一样使用这个accountService了。

0 请登录后投票
   发表时间:2008-06-15  

如果很不幸,你要求的业务逻辑比较复杂(这种情况比比皆是,业务逻辑本来就是为了处理比较繁琐的事情而存在,DAO封装了数据访问后,就可以在业务层安心的进行数据无关的业务操作)

那么,我们就可以用一个抽象类去实现接口中某些复杂业务的方法。其他的简单的业务逻辑的方法则仍可由Rapae去代理实现。

import java.util.List;
import org.springframework.orm.jpa.support.JpaDaoSupport;
import org.springframework.transaction.annotation.Transactional;
import test.com.rapae.model.Account;

@Transactional
public abstract class AccountServiceImpl extends JpaDaoSupport implements AccountService {
	
	public Account login(String username, String password) {
		List<?> result = getJpaTemplate().find("from Account where username = ? and password = ?", username, password);
		return (Account) (result.isEmpty()?null:result.get(0));
	}

}
 

当然,对应的Spring配置也得做出相应的修改

	<bean id="accountService"
		class="com.javaforge.rapae.factory.GenericRapaeFactoryBean">
		<property name="target"
			value="test.com.rapae.service.AccountServiceImpl" />
		<property name="rapaeProxyHandler" ref="rapaeProxyHandler" />
	</bean>

 我们可以看到,target属性由原来的test.com.rapae.service.AccountService改变为test.com.rapae.service.AccountServiceImpl,其他都不用做任何改变。


剩下的事情大家都可以猜到了:按正常由Spring托管的accountService使用。

0 请登录后投票
   发表时间:2008-06-15  

下面给出我对我的构想的一个基于JPA的参考实现,当然只要你实现了相应的接口就可以写出基于Hibernate或者基于其他ORM框架的实现。比较抱歉的是,我个人不打算将其做成XX框架(本来想做成Java On Rails的,看来....-_-!!),也没这个能力和时间。所以觉得,这个能作为一个弱化DAO层的方案提出,让大家在平时中能进行参考是最好的选择。

 

该参考实现只涉及到具体实现代码,不自带编译时需要的包。若想编译,请按列表中的包自行准备。

  1. antlr-2.7.6.jar
  2. asm-3.1.jar
  3. cglib-2.2.jar
  4. commons-collections.jar
  5. commons-logging.jar
  6. dom4j-1.6.1.jar
  7. ejb3-persistence.jar
  8. hibernate3.jar (版本3.2)
  9. hibernate-annotations.jar
  10. hibernate-commons-annotations.jar
  11. hibernate-entitymanager.jar (版本3.3.2.GA)
  12. javassist.jar
  13. jta.jar
  14. junit-4.4.jar
  15. log4j-1.2.15.jar
  16. ojdbc14.jar (这个是ORACLE的JDBC驱动,如果你用其他的数据库例如MySQL,请自行替换成对应的JDBC驱动)
  17. spring.jar (版本2.5.4)

若想跑起测试用例,请自行配置 persistence.xml

 

  • rapae.jar (26.8 KB)
  • 描述: 打包好的rapae
  • 下载次数: 77
  • Rapae-src.zip (85.4 KB)
  • 描述: Rapae的参考实现的源代码
  • 下载次数: 125
0 请登录后投票
   发表时间:2008-06-16  
http://www.iteye.com/post/488026

跟这个有什么区别?

而且你的代理是用CGLIB实现的, 仅仅是接口的话,完全不如动态代理来的舒服。
0 请登录后投票
   发表时间:2008-06-16  

HOHO,非常抱歉,我真的没注意看Norther也发表过类似的文章。我做毕业设计好久了,几个月都没关注JavaEye。不过确实是小弟独自构想的,只能说我们想得比较接近了,绝无抄袭之意。

 

对于Norther的疑问:

Norther 写道
而且你的代理是用CGLIB实现的, 仅仅是接口的话,完全不如动态代理来的舒服。

 我实际上是这样处理的:遇到接口的时候采用JDK Proxy,遇到类的时候用CGLib

 

	public Object getObject() throws Exception {
		if (getTarget().isInterface()) {
			// 如果是接口,则用JDK Proxy进行代理
			return getInterfaceRapaeProxy().proxy(getTarget(),
					getRapaeProxyHandler());
		} else {
			// 如果是类,则用CGLib进行代理
			Object object = getClassRapaeProxy().proxy(getTarget(),
					getRapaeProxyHandler());
			//自动封装
			applicationContext.getAutowireCapableBeanFactory()
					.autowireBeanProperties(object,
							AutowireCapableBeanFactory.AUTOWIRE_AUTODETECT,
							false);
			return object;
		}// if
	}

 

至于不同点,我大概看了一下Norther的帖子--估计一时半会不能完全领悟您帖子的精髓,所以只能用“大概”这种粗糙的词语--个人提出点不同点:

 

  1. Norther倾向的是对DAO进行开刀,Service层不进行涉及。当然这只是叫法上的区别,在我看来DAO层本来就应该和Service层进行合并。
  2. Norther的设计不能满足这个需求:当自动代理生成的DAO不能满足实际需求时,要进行手工编写代码。此时应该用一个抽象类去实现这个比较复杂的方法,其他方法照样由框架进行生成。
  3. 实现更多的封装,例如:翻页查询;根据默认约定的命名规则使用命名查询;
  4. 参数的注入上也有些许区别
  5. 对Spring的依赖注入的具体实现上也有所区别,我的依赖注入是采用了Spring自己的AutowireCapableBeanFactory来实现
    			//自动封装
    			applicationContext.getAutowireCapableBeanFactory()
    					.autowireBeanProperties(object,
    							AutowireCapableBeanFactory.AUTOWIRE_AUTODETECT,
    							false);
     缺点就是不够灵活,注入什么就全部交给Spring全权管理了。不过都已经交给框架自动生成了,这点灵活也算不了什么了。
  6. Norther不是线程安全。由于Norther是通过静态类去实现的
    /**
     * 
     * @author Norther Wang
     * @since 2008-3-11 下午11:34:32
     */
    public class DynamicDaoFactory {
    	public static Object create(Class<?> clazz, SessionFactory sessionFactory) {
    
    		HibernateDaoDelegator hibernateDaoDelegator = new HibernateDaoDelegator();
    		hibernateDaoDelegator.setSessionFactory(sessionFactory);
    		DynamicDaoInvocationHandler dynamicDaoInvocationHandler = new DynamicDaoInvocationHandler();
    		dynamicDaoInvocationHandler.setHibernateDao(hibernateDaoDelegator);
    		return Proxy.newProxyInstance(clazz.getClassLoader(), new Class[] { clazz }, dynamicDaoInvocationHandler);
    	}
    }
    
     所有的DynamicDaoProxy都会调用DynamicDaoFactory的这个静态 create 方法去产生代理,所以尽管在DynamicDaoProxy中有
    	public boolean isSingleton() {
    		return false;
    	}
     确保每次代理都产生不同实例,可惜的是每个实例最终都会create进行代理。但是如果对create加上synchronized进行同步,又势必会造成性能瓶颈。

目前就这些吧,恩恩,比较开心了。终于有人回复了...,而且也证明我的想法并不孤单,呵呵。THX

0 请登录后投票
   发表时间:2008-06-16  
DAO又被蹂躏了~~~
这种方式看起来真的非常简洁。
比起泛型DAO来最大的好处无非是改接口而已。不再是继承。
DAO和Service我一直检查合并在一起。所以采用泛型DAO并不会带来问题。
这样简洁的写法让我比较心动,但是仔细想一下,把那些注解放到代码中也只是一行代码的问题并不会带来多大的简化。
0 请登录后投票
   发表时间:2008-06-16  
呵呵,我那个东东不能满足的东西太多了,其就是根据Annotation描述的操作去组装Hibernate API,目的就是简化大部分简单的操作而根本不用写实现类,如果有复杂操作,那就自己再写个类去实现好了,至于你所说的写个抽象类,自己实现复杂的,然后抽象的简单操作用CGLIB去代理,我想这样可能也有他的用武之地,但是意义不大,你都利用Annatation去描述你的操作了,目的就是不想自己再去写一个类实现,现在的有了Spring,Hibernate,大多数dao的代码都非常简单,我自己写个抽象类,那剩下的简单的操作我写两行代码再实现一下也并不麻烦,例如createQuery("from Student s where s.name =?"),这种东西,没必要再用CGLIB代理一下,等于把那个HQL放到Annotation里,工作量区别都不大,因为都很简单,而你那样就不纯粹了,不过这都是小问题。

另外注入方面,没什么好说的,你那个功能确实好,确实应该提供这么一种和Spring结合更紧密的方法。

关于线程安全问题,我这方面比较薄弱,还看不出我那样有什么线程安全问题,我也不理解为什么要在create方法上加synchronized,麻烦请指明。

最后那个isSingleton始终返回false,是我笔误了,应该始终返回true,不好意思了,在程序中,有几个需要实现的接口,就应当有几个相应的代理,不多不少刚刚好,我在AutoInjectDynamicDaoBeanPostProcessor里的daoCache,也是这个意思。
0 请登录后投票
   发表时间:2008-06-16  
fireflyc 写道
DAO又被蹂躏了~~~
这种方式看起来真的非常简洁。
比起泛型DAO来最大的好处无非是改接口而已。不再是继承。
DAO和Service我一直检查合并在一起。所以采用泛型DAO并不会带来问题。
这样简洁的写法让我比较心动,但是仔细想一下,把那些注解放到代码中也只是一行代码的问题并不会带来多大的简化。


我的意思就是该兄弟的最后一句,其实简化的并不大,这个工具真正的意义在于没有实现类,楼主那样再写个实现类出来,就基本丧失了这个工具最大的意义。
0 请登录后投票
   发表时间:2008-06-16  
Norther 写道
关于线程安全问题,我这方面比较薄弱,还看不出我那样有什么线程安全问题,我也不理解为什么要在create方法上加synchronized,麻烦请指明。

 其实去学校的路上我又仔细想了想,由于Spring在加载配置的时候实际上是单线程的,并不会出现我所谓的多线程问题,是我自己敏感了。我自己在开始YY的时候并不打算写死在Spring框架中,所以考虑到了多线程的问题...现在回头想想其实挺多余..

如果要考虑多线程并发,之所以要加synchronized,就是要防止create方法在完成之前再次被其他线程调用,这个时候的状态就不可预知了。可能会被其他线程调用么?可以说是肯定会的,因为你是静态的方法,而所有在不同线程的实例都在企图同时调用他。


fireflyc 写道
这样简洁的写法让我比较心动,但是仔细想一下,把那些注解放到代码中也只是一行代码的问题并不会带来多大的简化。
 
Norther 写道
我的意思就是该兄弟的最后一句,其实简化的并不大,这个工具真正的意义在于没有实现类,楼主那样再写个实现类出来,就基本丧失了这个工具最大的意义。

 

厄...貌似被插中软肋了...我原本的构思是这样的:

 

  • 描述: 用接口来屏蔽简单而又必须的方法
  • 大小: 66.8 KB
0 请登录后投票
论坛首页 Java企业应用版

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