前言
在开发过程中,我们不难发现,客户的需求以及产品的定位对开发内容的走向有很大的决策作用,而这些往往需要在一开始就尽可能考虑周全和设计完善。为什么说是尽可能,因为我们都知道,需求这种东西,一言难尽...作为开发者,既然无法掌控需求的变更等因素,那我们就要把握好自身能决定的工具资源等,架构设计、技术选型等等。有的人可能会说,我才多久经验之类,架构什么的不都是leader们大佬们的事情么,选什么数据库用什么技术又不是我能决定的。如果你有这种想法,我只能觉得你说得很对。不知道大家在开发过程中,有没有遇到,某个场景或者效果用现有的工具技术你觉得无法实现或者很难实现,又或者你知道又可以实现的技术框架却不会用?echarts、POI等,对于我来说,都符合上述场景。总的来说,有的东西在开发过程中可以当作学习,有些东西却需要你额外花时间在工作之外去研究。当然,如果你说你工作中不管需要什么新技术,你一天之类就可以掌握,那也是能力。而且这不是嘲讽什么的,是实实在在的,我现在愈发觉得学习能力的重要性,他对一个人的潜在能力增长曲线的影响太大,当然你有能力却不学习,那也注定是一条直线射穿。Spring、Strus 2、Hibernate、Mybatis、Spring MVC是比较常见的Web项目会用到的框架,持久层目前来看主要有Hibernate、Mybatis、Spring data jpa等,目前我在用Spring data jpa,这个东西真的厉害,用起来比我以前的想法又上了一个台阶,而且跟hibernate支持的也很好,Hibernate本身也是jpa规范的实现。接下来文中项目中类与方法设计的思路是之前基于Hibernate的,正好回顾一下,认识下其中的不足,好结合现在用的Jpa改进。
项目结构一览
以web项目技术框架选型为Spring+Hibernate+Spring MVC为例,忽略配置以及web前端文件,后台结果最精简情况大致如下:
从数据库方向依次向前介绍:
1.domain层,这个东西的标准解释是领域模型,我们orm对象关系映射的实体类一般就放在该层下,即实体层,类似的叫法,entity、model、pojo等作用大抵如此;
2.repository层,数据库访问层,这个就是dao层,存放数据库访问操作的方法;
3.service层,业务逻辑层,用于存放业务逻辑方法的地方,与dao层相比,有的人可能开始会觉得这个service层没有什么必要,或者不太清楚两者的区别。首先关于两者区别:业务逻辑层,故名思意,他的重要定位是业务,业务需求什么,他就要提供什么,举个例子,dao层提供了你删除商品的方法和添加日志的方法,这两个方法分别对应有两个实体对象商品和日志,操作的也是数据库相应的单个表,但是实际情况是你通过单个方法总感觉哪里不对劲,业务场景下,如果需要你删除东西后会有相应的日志记录,而日志又不会凭空捏造,需要有事件对应。这时候,service层的作用就体现出来了,简单的可以这么理解,复杂的数据库处理你靠单个dao方法无法实现的时候,你在service层去构造这个方法就行,通过SpringIOC特性,在service层注入你需要的调用的dao方法所在的类或者接口,实际场景一般都是接口。其次关于必要性,我曾经也想过,直接在controller里面注入多个dao类或者接口不是也能达到想要的效果吗?Controller顾名思义,控制器,它在web结果中,主要起到一个接受和转发请求并控制的作用,当你的业务逻辑相对简单的时候,你觉得看不出多大区别,当实际项目业务逻辑相对复杂很多的情况下,这个controller就有点炸了,像个身兼多职操劳过度的苦工,另外结合上一个疑惑,还有一个很重要的概念就是事务,而Spring的事务管理做得也很到位,通过编程式事务管理或者声明式事务管理都可以实现,而事务一般就设置在业务逻辑也就是service层上,关于事务也是很重要和精髓的一块,需要学习也值得学习。
4.controller层,控制层,用struts 2也许会叫action层吧,或者通用点,叫web层其实也可以。他可以接收不同的请求url,调用相应的service层代码,操作数据库,跳转到制定页面,也可以不跳转,直接返回数据,这里的数据目前用json的比较多,典型的应用场景:ajax发起异步请求,DispatcherServlet捕获请求后对url进行解析,分发到相应的控制类中的相应方法中执行其中代码,该方法上加上@ResponseBody即可。
关于Hibernate在项目中的定位
Hibernate是一个典型的ORM持久层框架,ORM即Object Relational Mapping(对象关系映射),即实体类与数据库表之间的关系映射,通俗的讲,一张表对应一个实体类,一条记录对应一个实体类对象,字段对应实体类属性。对数据库的操作在Hibernate中有一个重要对象session,通过session封装一系列数据库操作方法在数据库访问层,所以在上面的结构中,Hibernate主要操作和作用的有domain层和repository层,下面示例也省略其他层代码。
示例
domain层,这里简单使用,Mysql数据库主键也用的int自增型,实际应用数据量较大等情况下会用varchar,保存使用UUID赋值主键。
@Entity
@Table(name="TBL_USER")
public class User {
private Integer id;
private String username;
private String password;
@Id
@GeneratedValue(strategy=GenerationType.AUTO)
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
@Override
public String toString() {
return "User [id=" + id + ", username=" + username + ", password=" + password + "]";
} }
repository层
BaseDao泛型接口
public interface BaseDao<T> {
public void edit(Object obj); //添加或者更新一条记录
public void delete(int id); //根据主键删除一条记录
public T load(int id); //根据主键查找一条记录(懒加载)
public T get(int id); //根据主键查找一条记录(非懒加载)
}
BaseDao泛型实现类
public class BaseDaoImpl<T> implements BaseDao<T> {
@Resource
private SessionFactory factory;
private Class<T> clazz = GeneriscUtil.getGenericType(this.getClass()); protected Session getSession() {
return factory.getCurrentSession();
} public void edit(Object obj) {
getSession().saveOrUpdate(obj);
} public void delete(int id) {
Object object = getSession().get(clazz, id);
if(object != null) {
getSession().delete(object);
}
} public T load(int id) {
return (T) getSession().load(clazz, id);
} public T get(int id) {
return (T) getSession().get(clazz, id);
} }
泛型工具类
public class GeneriscUtil {
@SuppressWarnings("rawtypes")
public static Class getGenericType(Class clazz){
Type genType = clazz.getGenericSuperclass();//获取泛型父类
Type[] types = ((ParameterizedType) genType).getActualTypeArguments();
if (!(types[0] instanceof Class)) {
return Object.class;
}
return (Class) types[0];
}
}
最后:UserDao接口和UserDaoImpl实现类
public interface UserDao extends BaseDao<User> {
User login(String username, String password);
}
@Repository("UserDao")
public class UserDaoImpl extends BaseDaoImpl<User> implements UserDao { public User login(String username, String password) {
String hql = "from User u where u.username=:un and u.password=:pwd";
Query query = getSession().createQuery(hql);
query.setString("un", username)
.setString("pwd", password);
Object obj = query.uniqueResult();
return obj != null ? (User)obj : null;
} }
还有其的XXXDao和XXXDaoImpl,通过继承和实现,可以让全部Impl实现类很轻松地就拥有基本的CRUD等通用数据库操作方法,如果某个实体有特殊的Dao操作只需要在相应Dao接口实现,然后在Impl实现类中实现该特殊方法就行,以前认识到的知识点在这里有了深入理解。
泛型是一种思想,也是一种技术,动态的获取类型,让编程更加灵活,这里利用泛型,我们可以先不制定实体类对象具体类型,构造一个泛型的Dao接口和实现类,让后面各自具体的实体Dao去继承泛型的时候再确定类型,从而也得到了泛型中定义的基本方法。
继承体现的是代码重用思想,当一个方法被构造多次的时候我们就要思考代码重用的问题了,在这里每个实体对象都需要基本的数据库操作方法,如果一个个的去定义将会十分繁琐和枯燥,通过继承我们可以省去很多代码。
实现不同于继承,它没有节省代码,而且又涉及到抽象的概念,在单个父类与多个子类的继承关系中,父类某个方法对应的子类实现有差异时,我们对于父类的该方法,即可定义为抽象方法,子类各自实现具体细节即可。举个例子:动物父类,子类有鸟、鱼等,定义动物时,会定义睡觉方法,但是每种动物睡觉情况都不一样,鸟睡树上,鱼睡水下,你没法指定具体实现细节,所以就可以定义一个抽象的睡觉方法void sleep();具体的动物实现接口后,重写该抽象方法,展示实现细节。
总结继承和实现:继承通用的,实现特殊的。在文中构造的持久层中,我们的所有实体通过继承轻松地拥有了所有的通用数据库操作方法,各自特殊的操作需求可在各自接口中生命然后在实现类中实现即可。
最后,文中的项目中其实还存在许多不足的地方,主键的类型其实也可以用泛型,dao层有通用组件,service层是否应该也有通用的,若是有该如何构造等等,一些问题我在现在用的spring data jpa中得到了答案,所以应该会另外梳理一下写出来。