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.data</groupId>
<artifactId>spring-data-jpa</artifactId>
<version>1.10.1.RELEASE</version>
</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<User,String> {
      // 这里不需要任何方法
}

 

使用JpaRepository绑定User实体(@Entity标注的实体类),并指定主键(ID)类型为String类型。此接口体内,不需要声明任何方法,直接使用由Spring Data默认提供的18个方法。

 

2、定义查询方法,JpaRepository规则支持的方法

UserRepository.java:
public  interface UserRepository extends JpaRepository<User,String> {
// 定义查询方法,供JpaRepository默认18个方法无法满足时
userName);
// 定对查询方法,根据email或mobile查询多条记录
email,String mobile);
}

 

这里的两个方法,虽然是开发者自行定义的,但是它们满足Spring Data的规则,所以开发者不需要具体实现这些方法,所有的工作都交给SpringData自动完成。

 

3、声明自定义查询方法,采用@Query封装SQL操作

UserRepository.java:
public  interface UserRepository extends JpaRepository<User,String> {
// 声明自定义查询,根据email模糊查询多条记录
@Query("select u from User u where u.email like '123456@%'")
    List<User> findAllEmailUsers();
}

需要特殊说明的是,在SpringData默认的DSL不能满足需求时,那么就需要结合其支持的拓展注解@Query来灵活自定义更为复杂的SQL操作,当然这里只是为了演示用法,仅仅是实现了模糊匹配的查询。另外,findAllEmailUsers方法是用户自定义的,此时可以不需要符合Spring Data的规则,可随意命名。

 

4、混合自定义操作方法,与降级EntityManager结合

UserRepository.java:
public  interface UserRepository extends JpaRepository<User,String> , UserExtRepository {
     // 这里不需要任何方法
}

 

这里主要是在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) {
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<User,String>,UserExtRepository {

   

// 定义查询方法,供JpaRepository默认18个方法无法满足时
userName);
    
// 定对查询方法,根据email或mobile查询多条记录
 email,String mobile);
    
// 声明自定义查询,根据email模糊查询多条记录
@Query("select u from User u where u.email like '123456@%'")
    List<User> findAllEmailUsers();
    
}
 
新增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) {
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) {
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<User>  findByEmailOrMobile(String email,String mobile) {
       return userRepository.findByEmailOrMobile(email, mobile);
    }
    
// 根据email模糊匹配
    public List<User>  findAllEmailUsers() {
       return userRepository.findAllEmailUsers();
    }
    
// 查询所有记录
    public List<User>  findUsersNoPage() {
       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;
    
// 操作页面
@RequestMapping(value="/show",method=RequestMethod.GET)
    public ModelAndView show() throws Exception {
// 业务逻辑处理
result =  userRepositoryService.findUsersNoPage();
// 页面数据渲染
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 = 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);
// 页面数据渲染
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 = 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);
// 页面数据渲染
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 = new User();
user.setId(request.getParameter("id"));
// 业务逻辑处理
user2 =  userRepositoryService.findUserByID(user);
result = new ArrayList<User>();
result.add(user2);
// 页面数据渲染
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 = new User();
user.setUsername(request.getParameter("username"));
// 业务逻辑处理
userTmp =  userRepositoryService.findByUsername(user.getUsername());
result = new ArrayList<User>();
result.add(userTmp);
// 页面数据渲染
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 = new User();
user.setEmail(request.getParameter("email"));
user.setMobile(request.getParameter("mobile"));
// 业务逻辑处理
result =  userRepositoryService.findByEmailOrMobile(user.getEmail(), user.getMobile());
// 页面数据渲染
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 {
// 业务逻辑处理
result =  userRepositoryService.findAllEmailUsers();
// 页面数据渲染
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 = new User();
user.setId(request.getParameter("id"));
// 业务逻辑处理
userRepositoryService.delete(user);
// 页面数据渲染
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 {
email =  request.getParameter("email");
// 业务逻辑处理
userRepositoryService.batchUpdate(email);
// 页面数据渲染
mav = new ModelAndView("test/user_operation");
mav.addObject("result", userRepositoryService.findUsersNoPage());
       return mav;
    }
    
}

  

4、View页面

具体的页面部分,请读者结合上一篇的jsp页面进行修改实现,具体实现的效果会在下面逻辑,读者也可以自行设计布局。 

整体效果如下:

spring data jpa 注入的缓存 spring data jpa hibernate_jpa

 

A、新增一条记录

spring data jpa 注入的缓存 spring data jpa hibernate_java_02

 

新增cwteam7记录

 

B、修改一条记录

spring data jpa 注入的缓存 spring data jpa hibernate_jpa_03

修改cwteam7的mobile值

 

C、根据ID查询

spring data jpa 注入的缓存 spring data jpa hibernate_java_04

 

根据ID查询cwteam7记录

 

D、删除一条记录

spring data jpa 注入的缓存 spring data jpa hibernate_jpa_05

删除cwteam7这条记录

 

E、根据用户名查询

spring data jpa 注入的缓存 spring data jpa hibernate_hibernate_06

根据用户名查询cwteam5记录

 

F、根据email或mobile查询

spring data jpa 注入的缓存 spring data jpa hibernate_框架_07

 

 

G、根据email模糊查询

spring data jpa 注入的缓存 spring data jpa hibernate_spring_08

查询条件,请查看UserRepository代码部分。

 

H、匹配email批量修改

spring data jpa 注入的缓存 spring data jpa hibernate_java_09

 

修改的细节,请查看UserRepositoryImpl代码部分。

 

 

好了,由于作者水平有限,如有不正确或是误导的地方,请不吝指出讨论