Spring整合Hibernate实现Spring Data JPA
在上一篇文章《Spring整合Hibernate实现JPA持久化》中,我们已经介绍了怎样在Spring容器中合理地集成Hibernate来实现JPA的ORM机制。但是,细心的读者会发现,上一篇文章中使用了EntityManager来直接与数据库交互,存在这一定的耦合度,更重要的是每当新增或修改新的实体Entity的管理时,都需要重复的实现EntityManager在Dao层的逻辑,有些过于模版化,重复劳动的问题存在,所以这篇文章就来介绍下解决这个问题所采用的Spring Data来自动化JPA的实现,听着很牛是吧!
l 基本储备技术介绍
l Spring Data如何实现JPA
l Spring Data JPA重构实例
一、基础储备技术介绍
在介绍如何实现之前,很有必要先介绍些储备内容。
首先,我们需要在Maven中引入spring-data-jpa软件包,参看如下内容:
<dependency>
<groupId>org.springframework.datagroupId>
<artifactId>spring-data-jpaartifactId>
<version>1.10.1.RELEASEversion>
dependency>
这里使用了1.10.1.RELEASE版本,注意不同版本与Spring和Hibernate版本的兼容,读者只需要按照上篇文章及这里的介绍实现即可。在这个软件包里,提供了JpaRepository接口(继承自Repository),它的作用就是留给开发者自定义Dao层接口时进行拓展继承的,其用来绑定@Entity模型以及模型对应的ID类型的,这样绑定声明后,在程序或容器启动时进行初始化时,Spring Data根据@Entity注解找到该实体,并通过反射和注解技术来获取实体类的类名字、属性以及方法等参数,然后通过其默认提供的18个方法API(这里不做介绍,读者可执行查阅资料了解),来实现对实体的管理及持久化的封装。
其次,Spring Data默认生成的实体持久化方法遵循着一种模式,有助于生成针对数据库的查询,比如:
findUserByFirstnameAndLastnameOrderByLastname()
针对这个方法,SpringData是这样的规定的:find/get/read是查询动词,不论使用哪个,功能都是一样的;User代表主题,指的是被定义为@Entity实体的类名;By仅仅是一个连接符而已,其主要是用来连接查询条件(断言),以及断言内查询条件的连接,比如:OrderByLastname;FirstnameAndLastnameOrderByLastname则比较明显,是查询SQL的条件断言,这里指的根据User的Firstname和Lastname查询记录,并根据Lastname生序排列结果,另外,还可以忽略断言条件的大小写,以及判断过滤等,具体里面的内容较多,建议读者查阅相关资料熟悉了解。
最后,我们来简单介绍下DSL(领域驱动语言):其思想主要是“求专不求全”,不像其它通用语言那样涵盖一切范围的问题,而是专门针对某一特定问题的计算机语言。在本质上,Spring Data中已经定义了一组小规模的领域驱动语言,并且持久化的细节支持,都是通过Repository方法的签名来描述的。但是,DSL也有其局限性,在其所提供的实体查询方法无法满足需求时,我们就得借助于@Query等来实现更为复杂的需求,具体会在下面实现部分介绍。
二、Spring Data如何实现JPA
1、定义查询方法,JpaRepository提供的CURD方法
UserRepository.java:
public interface UserRepository extends JpaRepository
// 这里不需要任何方法
}
使用JpaRepository绑定User实体(@Entity标注的实体类),并指定主键(ID)类型为String类型。此接口体内,不需要声明任何方法,直接使用由Spring Data默认提供的18个方法。
2、定义查询方法,JpaRepository规则支持的方法
UserRepository.java:
public interface UserRepository extends JpaRepository
// 定义查询方法,供JpaRepository默认18个方法无法满足时
User findByUsername(String userName);
// 定对查询方法,根据email或mobile查询多条记录
List
}
这里的两个方法,虽然是开发者自行定义的,但是它们满足Spring Data的规则,所以开发者不需要具体实现这些方法,所有的工作都交给SpringData自动完成。
3、声明自定义查询方法,采用@Query封装SQL操作
UserRepository.java:
public interface UserRepository extends JpaRepository
// 声明自定义查询,根据email模糊查询多条记录
@Query("select u from User u where u.email like '123456@%'")
List
}
需要特殊说明的是,在SpringData默认的DSL不能满足需求时,那么就需要结合其支持的拓展注解@Query来灵活自定义更为复杂的SQL操作,当然这里只是为了演示用法,仅仅是实现了模糊匹配的查询。另外,findAllEmailUsers方法是用户自定义的,此时可以不需要符合Spring Data的规则,可随意命名。
4、混合自定义操作方法,与降级EntityManager结合
UserRepository.java:
public interface UserRepository extends JpaRepository
// 这里不需要任何方法
}
这里主要是在SpringData默认方法,以及@Query都不能满足需求时(虽然很少),就需要降级使用老版的EntityManager来实现了,当然,虽然结合使用了EntityManager,也就需要实现对应的接口,这里新建立了一个UserExtRepository接口,具体内容如下:
public interface UserExtRepository {
// 单一的Spring Data JPA无法完成的操作,
// 需要降低版本,结合EntityManager协同完成
public int batchUpdate(String email);
}
那么,接下来就得提供一个它的实现类:UserRepositoryImpl了,内容如下:
@Repository("UserRepositoryImpl")
public class UserRepositoryImpl implements UserExtRepository {
@PersistenceContext
private EntityManager em;
@Override
public intbatchUpdate(String email) {
String update = "update User u set u.status=1 where u.email='"+email+"'";
return em.createQuery(update).executeUpdate();
}
}
需要注意的是,SpringData在结合EntityManager实现JPA时,需要嵌入了EntityManager的实现类,如:UserRepositoryImpl,需要满足Spring Data的命名规则,其默认必须是采用xxxImpl的形式才能被识别,如果需要定义为别的格式命名,只需要在@EnableJpaRepositories开支持时(在Spring上下文配置中声明,这里使用的是JavaConfig方式),指定命名格式即可,具体如下实现:
@EnableJpaRepositories(basePackages="com.cwteam.orm.dao",repositoryImplementationPostfix="Helper")
同时,还有个需要注意的是,既然你想在Spring Data中混合使用EntityManager的功能,那么你的实现类的命名需要有一定规则,如:UserRepositoryImpl,其与我们继承JpaRepository的自定义类UserRepository相对应吧!另外,这里的basePackages的作用比较显示,用来指定扫描初始化Entity的位置。
三、Spring Data JPA重构实例
在完成了上面所有工作之后,接下来具体介绍下针对上一篇文章的例子重构,实际上改动量较小,主要修改Dao相关,以及控制器调用相关,具体各个新增或修改的内容已经在上面第二部分有介绍,这里直接罗列代码!
1、Dao层相关
新增UserRepository.java:
public interface UserRepository extends JpaRepository
// 定义查询方法,供JpaRepository默认18个方法无法满足时
User findByUsername(String userName);
// 定对查询方法,根据email或mobile查询多条记录
List
// 声明自定义查询,根据email模糊查询多条记录
@Query("select u from User u where u.email like '123456@%'")
List
}
新增UserExtRepository.java:
public interface UserExtRepository {
// 单一的Spring Data JPA无法完成的操作,
// 需要降低版本,结合EntityManager协同完成
public int batchUpdate(String email);
}
新增UserRepositoryImpl.java:
@Repository("UserRepositoryImpl")
public class UserRepositoryImpl implements UserExtRepository {
@PersistenceContext
private EntityManager em;
@Override
public int batchUpdate(String email) {
String update = "update User u set u.status=1 where u.email='"+email+"'";
return em.createQuery(update).executeUpdate();
}
}
2、Service层
新增UserRepositoryService.java:
@Service("UserRepositoryService")
public class UserRepositoryService {
@Autowired
private UserRepository userRepository;
// 新增记录
@Transactional
public void saveUser(User user) {
userRepository.save(user);
}
// 更新记录
@Transactional
public void updateUser(User user) {
User result = findUserByID(user);
result.setUsername(user.getUsername());
result.setPassword(user.getPassword());
result.setEmail(user.getEmail());
result.setMobile(user.getMobile());
}
// 根据ID查询记录
public User findUserByID(User user) {
return userRepository.findOne(user.getId());
}
// 根据用户名查询
public User findByUsername(String username) {
return userRepository.findByUsername(username);
}
// 根据邮箱或手机查询
public List
return userRepository.findByEmailOrMobile(email, mobile);
}
// 根据email模糊匹配
public List
return userRepository.findAllEmailUsers();
}
// 查询所有记录
public List
return userRepository.findAll();
}
// 根据用户名删除记录
@Transactional
public void delete(User user) {
userRepository.delete(user);
}
// 批量修改status状态
@Transactional
public int batchUpdate(String email) {
return userRepository.batchUpdate(email);
}
}
3、Controller层
UserController.java:
@Controller
@RequestMapping("/user")
public class UserController {
@Autowired
UserRepositoryService userRepositoryService;
// 操作页面
@RequestMapping(value="/show",method=RequestMethod.GET)
public ModelAndView show() throws Exception {
// 业务逻辑处理
List
// 页面数据渲染
ModelAndView mav = new ModelAndView("test/user_operation");
mav.addObject("result", result);
return mav;
}
// 新增记录
@RequestMapping(value="/save",method=RequestMethod.POST)
public ModelAndView save(HttpServletRequest request) throws Exception {
User user = new User();
user.setUsername(request.getParameter("username"));
user.setPassword(request.getParameter("password"));
user.setMobile(request.getParameter("mobile"));
user.setEmail(request.getParameter("email"));
// 业务逻辑处理
userRepositoryService.saveUser(user);
// 页面数据渲染
ModelAndView mav = new ModelAndView("test/user_operation");
mav.addObject("result", userRepositoryService.findUsersNoPage());
return mav;
}
// 更新记录
@RequestMapping(value="/update",method=RequestMethod.POST)
public ModelAndView update(HttpServletRequest request) throws Exception {
User user = new User();
user.setId(request.getParameter("id"));
user.setUsername(request.getParameter("username"));
user.setPassword(request.getParameter("password"));
user.setMobile(request.getParameter("mobile"));
user.setEmail(request.getParameter("email"));
// 业务逻辑处理
userRepositoryService.updateUser(user);
// 页面数据渲染
ModelAndView mav = new ModelAndView("test/user_operation");
mav.addObject("result", userRepositoryService.findUsersNoPage());
return mav;
}
// 根据ID查询
@RequestMapping(value="/find",method=RequestMethod.GET)
public ModelAndView find(HttpServletRequest request) throws Exception {
User user = new User();
user.setId(request.getParameter("id"));
// 业务逻辑处理
User user2 = userRepositoryService.findUserByID(user);
List
result.add(user2);
// 页面数据渲染
ModelAndView mav = new ModelAndView("test/user_operation");
mav.addObject("result", result);
return mav;
}
// 根据用户名查询
@RequestMapping(value="/findbyusername",method=RequestMethod.GET)
public ModelAndView findByUsername(HttpServletRequest request) throws Exception {
User user = new User();
user.setUsername(request.getParameter("username"));
// 业务逻辑处理
User userTmp = userRepositoryService.findByUsername(user.getUsername());
List
result.add(userTmp);
// 页面数据渲染
ModelAndView mav = new ModelAndView("test/user_operation");
mav.addObject("result", result);
return mav;
}
// 根据email或mobile查询
@RequestMapping(value="/findbyemailormobile",method=RequestMethod.GET)
public ModelAndView findByEmailOrMobile(HttpServletRequest request) throws Exception {
User user = new User();
user.setEmail(request.getParameter("email"));
user.setMobile(request.getParameter("mobile"));
// 业务逻辑处理
List
// 页面数据渲染
ModelAndView mav = new ModelAndView("test/user_operation");
mav.addObject("result", result);
return mav;
}
// 根据email模糊匹配查询
@RequestMapping(value="/findallemailusers",method=RequestMethod.GET)
public ModelAndView findAllEmailUsers(HttpServletRequest request) throws Exception {
// 业务逻辑处理
List
// 页面数据渲染
ModelAndView mav = new ModelAndView("test/user_operation");
mav.addObject("result", result);
return mav;
}
// 根据ID删除记录
@RequestMapping(value="/delete",method=RequestMethod.GET)
public ModelAndView delete(HttpServletRequest request) throws Exception {
User user = new User();
user.setId(request.getParameter("id"));
// 业务逻辑处理
userRepositoryService.delete(user);
// 页面数据渲染
ModelAndView mav = new ModelAndView("test/user_operation");
mav.addObject("result", userRepositoryService.findUsersNoPage());
return mav;
}
// 根据email匹配批量修改
@RequestMapping(value="/batchupdate",method=RequestMethod.POST)
public ModelAndView extUpdate(HttpServletRequest request) throws Exception {
String email = request.getParameter("email");
// 业务逻辑处理
userRepositoryService.batchUpdate(email);
// 页面数据渲染
ModelAndView mav = new ModelAndView("test/user_operation");
mav.addObject("result", userRepositoryService.findUsersNoPage());
return mav;
}
}
4、View页面
具体的页面部分,请读者结合上一篇的jsp页面进行修改实现,具体实现的效果会在下面逻辑,读者也可以自行设计布局。
整体效果如下:
A、新增一条记录
新增cwteam7记录
B、修改一条记录
修改cwteam7的mobile值
C、根据ID查询
根据ID查询cwteam7记录
D、删除一条记录
删除cwteam7这条记录
E、根据用户名查询
根据用户名查询cwteam5记录
F、根据email或mobile查询
G、根据email模糊查询
查询条件,请查看UserRepository代码部分。
H、匹配email批量修改
修改的细节,请查看UserRepositoryImpl代码部分。
好了,由于作者水平有限,如有不正确或是误导的地方,请不吝指出讨论(技术交流群:497552060(新))