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个方法无法满足时

    User findByUsername(String userName);

    // 定对查询方法,根据emailmobile查询多条记录

    List<User>  findByEmailOrMobile(String 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) {

       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<User,String>,UserExtRepository {

   

    // 定义查询方法,供JpaRepository默认18个方法无法满足时

    User findByUsername(String userName);

   

    // 定对查询方法,根据emailmobile查询多条记录

    List<User> findByEmailOrMobile(String 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) {

       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<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 userRepositoryService;

   

    // 操作页面

    @RequestMapping(value="/show",method=RequestMethod.GET)

    public ModelAndView show() throws Exception {

       // 业务逻辑处理

       List<User> result = userRepositoryService.findUsersNoPage();

       // 页面数据渲染

       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<User> result = new ArrayList<User>();

       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<User> result = new ArrayList<User>();

       result.add(userTmp);

       // 页面数据渲染

       ModelAndView mav = new ModelAndView("test/user_operation");

       mav.addObject("result", result);

       return mav;

    }

   

    // 根据emailmobile查询

    @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<User> result = userRepositoryService.findByEmailOrMobile(user.getEmail(), user.getMobile());

       // 页面数据渲染

       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<User> result = userRepositoryService.findAllEmailUsers();

       // 页面数据渲染

       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(新))

 

 

Logo

权威|前沿|技术|干货|国内首个API全生命周期开发者社区

更多推荐