在使用Struts+Hibernate+Spring框架时,发现Spring为应用程序生成的数据访问层的代码都非常的重复,这样给人的感觉非常的不爽,基本上有多少个DAO,每个DAO都是重复的方法,无非就是换了一下具体的相对应的pojo类而已,这样感觉非常违背java代码重用的原则,因此想对此做一下改进,以便让代码的重用率更高一些,真实体现java在方法重用上的优雅性, 我想到了用java反射机制来实现,下面就是具体的代码片段了(关于java反射机制不懂的朋友,可以参考我原来写的关于java反射机制的文章)。
看下面一端代码,它是用来保存Diary这个对象的方法,如果你有一个User,或者Department,或者更多需要保存的对象的话,那么在你的相应的UserDAO,DepartmentDAO类里面下面的方法仍然会再出现一遍,不知道你看着爽不爽,至少我的感觉是非常冗余。
public
void
save(Diary transientInstance)
...
{
log.debug("saving Diary instance");

try ...{
getHibernateTemplate().save(transientInstance);
log.debug("save successful");

} catch (RuntimeException re) ...{
log.error("save failed", re);
throw re;
}
}
接着我来进行改进,我让所有的具体的DAO类都去实现一个BaseDAO类,在BaseDAO类里面上述方法将如下所示:
/** */
/**
* 保存对象
*
* @param object
*/

public
void
save(Object object)
...
{
log.info("保存 " + object.getClass().getName() + " 实例");

try ...{
getHibernateTemplate().save(object);
log.info("保存 " + object.getClass().getName() + " 实例成功");

} catch (RuntimeException re) ...{
log.info("保存 " + object.getClass().getName() + " 实例失败", re);
throw re;
}
}
上述方法还没真正的使用java反射机制,只是在打印保存对象时,用了一点点,那么接着看下面的代码吧,你就会感受到java反射机制所带来的好处!
按照Id查询对象:
/**
* 按照id查询对象
*
*
@param
object
*
@param
id
*
@return
*/
public
Object findById(Class object, java.lang.Integer id) {
log.info(
"
通过:
"
+
id
+
"
查询
"
+
object.getCanonicalName()
+
"
实例
"
);
try
{
Object instance
=
(Object) getHibernateTemplate().get(
object.getCanonicalName(), id);
return
instance;
}
catch
(RuntimeException re) {
log.info(
"
通过:
"
+
id
+
"
查询
"
+
object.getCanonicalName()
+
"
实例失败
"
,
re);
throw
re;
}
}
一直记得hibernate按照id来查询对象的方式,如 get("Teacher",new Integer(1)),这样如此硬编码的方式在java反射机制面前居然也变的如此灵活,真的是爱上java反射机制没的说。
接着往下看吧
按照example查询对象,这没什么好说的,就算没有java的反射照样可以完成,只是有了反射之后,更便于我们知道哪里出了问题
/**
* 按照example查询
*
*
@param
instance
*
@return
*/
public
List findByExample(Object instance) {
log.info(
"
使用Example查询
"
+
instance.getClass().getName()
+
"
实例
"
);
try
{
List results
=
getHibernateTemplate().findByExample(instance);
log.info(
"
使用Example查询
"
+
instance.getClass().getName()
+
"
实例成功, 查询出的记录数为:
"
+
results.size());
return
results;
}
catch
(RuntimeException re) {
log.info(
"
使用Example查询
"
+
instance.getClass().getName()
+
"
实例失败
"
,
re);
throw
re;
}
}
接下来就是一个使用反射的最好的案例了
按照属性查询:按照这种方式做查询的再普通不过了,要使用jdbc的话不知道我们要写多少条String版的sql语句,现在有了spring帮着管理,真的轻松很多,看看吧
认真揣摩这个方法,发现这个方法的代码写的还是相当流畅和优雅,至少我写的话,绝对会非常累赘,里面使用了java反射机制,从而使这个方法变的更通用,在做项目的时候,我在根据不同条件求count的时候,没有认真看这个方法,因为当时遇到的问题是如果使用Hql来做查询时,如果条件有中文出现的话,中文条件将被当作乱码,后来被逼的没办法,居然写成下面的代码:
Query query
=
null
;
String hql
=
"
select count(*) from Student as s
"
;
query
=
session.createQuery(hql);

if
(stu.getStatuId()
!=
null
)
...
{
hql=hql+" where s.studentStatu.statuId="+stu.getStatuId();
query=session.createQuery(hql);
}

if
(stu.getClassId()
!=
null
)
...
{
hql=hql+" and s.classes.classesId="+stu.getClassId();
query=session.createQuery(hql);
}

if
(stu.getTeacherByDegreeTeacherId()
!=
null
)
...
{
hql=hql+" and s.teacherByDegreeTeacherId.teacherId="+stu.getTeacherByDegreeTeacherId().getTeacherId();
query=session.createQuery(hql);
}

if
(stu.getStuNo()
!=
null
)
...
{
hql=hql+" and s.stuNo='"+stu.getStuNo()+"'";
query=session.createQuery(hql);
}

if
(stu.getEmployeWay()
!=
null
)
...
{
hql=hql+" and s.employeWay='"+stu.getEmployeWay()+"'";
query=session.createQuery(hql);
}

if
(stu.getCreateDate()
!=
null
)
...
{
String date=stu.getCreateDate().toString();
hql=hql+" and s.createDate='"+date+"'";
query=session.createQuery(hql);
}

if
(stu.getIsObtain()
!=
null
)
...
{
hql=hql+" and s.isObtain=?";
query=session.createQuery(hql);
query.setString(0, stu.getIsObtain());
}

if
(stu.getWorkIntentCity()
!=
null
)
...
{
hql=hql+" and s.workIntentCity=?";
query=session.createQuery(hql);
query.setString(0, stu.getWorkIntentCity());
}

if
(stu.getAddress()
!=
null
)
...
{
hql=hql+" and s.address like ?";
query=session.createQuery(hql);
query.setString(0, "%"+stu.getWorkIntentCity()+"%");
}

if
(stu.getStuName()
!=
null
)
...
{
hql=hql+" and s.stuName=?";
query=session.createQuery(hql);

if(stu.getWorkIntentCity()!=null)...{
query.setString(0, stu.getWorkIntentCity());
query.setString(1, stu.getStuName());

}else...{
query.setString(0, stu.getStuName());
}
}
System.out.println(
"
-----------hql----------------------:
"
+
hql);
return
query.list();
只所以把代码写的如此愚蠢,就是因为一个原因:query.setString()这个方法,因为通过它把中文加入查询条件的话,就不会出现乱码了,而为了达到这一目的,代码就被我写成这种模样,想想当时我还自以为是的样子真是可笑,我都没把hibernate里面的方法整明白就乱写一通,为什么就不知道find(String,Object [] values)这个方法已经存在了呢?我只想到有find(String,object value),晕!
不过幸好后来意识到这样写的坏处,根据条件求count的方法又变成下面的这种方式
/** */
/**
* 根据DetachedCriteria来获取符合条件的人数
* @param s
* @return int对象所在的集合
*/
public
int
getTotalCountByDetachedCriteria(Student s)

...
{
log("根据DetachedCriteria来获取符合条件的人数");
DetachedCriteria dc = DetachedCriteria.forClass(Student.class);

/**//*ProjectionList proList = Projections.projectionList();
proList.add(Projections.count("stuId"));
dc.setProjection(proList);
*/
//设置要查询的属性,即count
dc.setProjection(Projections.count("stuId"));
//设置查询条件
//如果直接根据Example来创建的话,无法把stu里面的非空属性赋为查询条件,所以只有根据是否非空一步步加入
//这一点没有criteria做的好,因为是criteria的话,根据dc.add(Example.create(s))会自动把非空属性加入where条件里面,
//不知道为什么DetachedCriteria里面没有这种方法呢?浪费那么多时间编些没有意义的代码
//dc.add(Example.create(s));
if(s.getStuName()!=null)
dc.add(Expression.eq("stuName", s.getStuName()));
if(s.getStatuId()!=null)
dc.add(Expression.eq("statuId", s.getStatuId()));
if(s.getClasses()!=null)
dc.add(Expression.eq("classes", s.getClasses()));
if(s.getWorkIntentCity()!=null)
dc.add(Expression.eq("workIntentCity", s.getWorkIntentCity()));
if(s.getStuNo()!=null)
dc.add(Expression.eq("stuNo", s.getStuNo()));
if(s.getEmployeWay()!=null)
dc.add(Expression.eq("employeWay",s.getEmployeWay()));
if(s.getIsObtain()!=null)
dc.add(Expression.eq("isObtain", s.getIsObtain()));
if(s.getTeacherByDegreeTeacherId()!=null)
dc.add(Expression.eq("teacherByDegreeTeacherId", s.getTeacherByDegreeTeacherId()));
List list = stuDAO.findCountByDetachedCriteria(dc);
log("---------------根据DetachedCriteria来获取totalcount-----"+ Integer.valueOf(list.get(0).toString()));
return list.size() == 0 ? null : Integer.valueOf(list.get(0).toString());
}
这样写的方式比上面写的方式稍微改进了一点点,至少把业务逻辑写在了业务层,而不像上面的那种方式在持久层做判断,但缺点似乎还是没有被改进—没法做为公共方法,这样造成的结果求不同类别的count这种方法都得重新写一遍,造成代码太过冗余,试着优化一下吧,优化后的方式如下:
public
Integer findCountByCriteria(
final
Object o){
try
{
return
(Integer) getHibernateTemplate().execute(
new
HibernateCallback()
{
public
Object doInHibernate(Session session)
throws
HibernateException
{
log.info(
"
o.getClass()
"
+
o.getClass().toString());
Field [] f
=
o.getClass().getFields();
for
(
int
i
=
0
;i
<
f.length;i
++
)
System.out.println(f[i].getName()
+
"
-oo
"
);
Criteria criteria
=
session.createCriteria(o.getClass());
criteria.add(Example.create(o));
log.info(criteria.toString());
criteria.setProjection(Projections.count(
"
stuId
"
));
return
criteria.list().get(
0
);
}
},
true
);
}
catch
(RuntimeException re)
{
throw
re;
}
}
仔细的分析这个代码段,唯一的缺陷就是设置查询的字段这个地方是硬编码的形式,其他的代码几乎都是用反射来实现的,因此这个方法也可以作为一个公有的方法放在BaseDAO中,这样在求类似的count值时就可以避免把相似的代码写N遍,还有一个好处是因为使用了Criteria这个类,因此就不再需要判断哪些条件该加,哪些条件不该加,这样可以减少大部分的无用乃至枯燥的if语句,但这个方法的不足之处就是在设置查询的字段时因为不能写成Projections.count("*")的形式,所以必须得赋你要查询的类的一个字段,如果要使用反射机制的话,这个字段必须是公共的,因为使用getField()方法取得的类变量是public类型的,我们可以让所有的pojo都继承一个类,在这个类中声明一个public类型的变量,这样问题就可以得到解决
好了,这些都是在做项目中遇到的和解决的,仅此拿出来互相分享和讨论,因为一贯欣赏java的开源原则,呵呵,虽然我的代码很乱,甚至丑陋,但毕竟还在学习,所以相信会写出优雅的代码的一天