关于 Spring For All

关于 Spring For All

Spring For All 的一切
最新动态

最新动态

Spring 5 会是咋样呢
Spring Boot

Spring Boot

快速构建并运行 Spring 应用程序
Spring Cloud

Spring Cloud

分布式系统的一套工具,可用于构建微服务
Spring Framework

Spring Framework

提供依赖注入、事务、Web应用、数据访问等模块
Spring Data

Spring Data

提供一致性数据访问模块
Spring Security

Spring Security

提供应用身份验证和授权支持
Spring Batch

Spring Batch

提供高容批处理操作模块
Spring AMQP

Spring AMQP

基于AMQP消息解决方案
Micro Service Arch.

Micro Service Arch.

微服务架构相关
开源项目及视频教程

开源项目及视频教程

做中国最好的 Spring 开源项目及视频教程
小马哥专栏

小马哥专栏

阿里技术布道者,专注 Spring Boot 及微服务

Spring Data JPA系列:创建查询(Query creation)

wayne 发表了文章 • 3 个评论 • 362 次浏览 • 2017-06-29 17:40 • 来自相关话题

通常,JPA的查询创建机制如查询方法所描述的那样工作。下面是一个关于JPA查询方法的简单示例:
1、在CustomerRepository接口中,有如下定义:$(document).ready(function() {$('pre code').each(function(i, block) { hljs.highlightBlock( block); }); }); /**
* 根据lastName查询结果
* @param lastName
* @return
*/
List<Customer> findByLastName(String lastName);这是通过参数lastName去解析,并将查询结果返回,语义相当于select * from customer where last_name=${lastName},在controller中就可以直接进行使用该方法了。
 
2、在CustomerController代码中使用CustomerRepository进行数据查询操作。如下: /**
* 查询lastName为指定用户昵称
*/
@RequestMapping("/findByLastName")
public void findByLastName(){
List<Customer> result = repository.findByLastName("Bauer");
for (Customer customer:result){
System.out.println(customer.toString());
}
System.out.println("-------------------------------------------");
}3、创建查询的方法名命名指南

同时,查询创建还提供了一些基本的方法命名规则,如下:













 
通过以上,可以通过简单的方法命名来实现查询,但是在使用的过程中,很多时候我们会不满足这个状态,因为可能好多的数据集按照这个来实现是极其费劲的。
接下来的篇幅将会讲JPA查询的重点内容,算是深入一点点,敬请期待吧。

参考:
官方文档,https://docs.spring.io/spring-data/jpa/docs/current/reference/html

DEMO示例:https://github.com/icnws/spring-data-jpa-demo
 
个人博客:http://www.icnws.com 查看全部
通常,JPA的查询创建机制如查询方法所描述的那样工作。下面是一个关于JPA查询方法的简单示例:
1、在CustomerRepository接口中,有如下定义:
    /**
* 根据lastName查询结果
* @param lastName
* @return
*/
List<Customer> findByLastName(String lastName);
这是通过参数lastName去解析,并将查询结果返回,语义相当于select * from customer where last_name=${lastName},在controller中就可以直接进行使用该方法了。
 
2、在CustomerController代码中使用CustomerRepository进行数据查询操作。如下:
    /**
* 查询lastName为指定用户昵称
*/
@RequestMapping("/findByLastName")
public void findByLastName(){
List<Customer> result = repository.findByLastName("Bauer");
for (Customer customer:result){
System.out.println(customer.toString());
}
System.out.println("-------------------------------------------");
}
3、创建查询的方法名命名指南

同时,查询创建还提供了一些基本的方法命名规则,如下:

c1.png

c2.png

c3.png

 
通过以上,可以通过简单的方法命名来实现查询,但是在使用的过程中,很多时候我们会不满足这个状态,因为可能好多的数据集按照这个来实现是极其费劲的。
接下来的篇幅将会讲JPA查询的重点内容,算是深入一点点,敬请期待吧。

参考:
官方文档,https://docs.spring.io/spring-data/jpa/docs/current/reference/html

DEMO示例:https://github.com/icnws/spring-data-jpa-demo
 
个人博客:http://www.icnws.com

Spring Data JPA系列:继承的方法

wayne 发表了文章 • 0 个评论 • 309 次浏览 • 2017-06-29 11:05 • 来自相关话题

上一节讲了spring-data-jpa的基本配置,这一节主要是讲自定义Repository继承了JpaRepository相关的一些方法,包括基本的增删改查方法。

1、方法列表

1)、从PagingAndSortingRepository继承的findAll方法
2)、从CrudRepository继承的count, delete,deleteAll, exists, findOne, save等方法
3)、从QueryByExampleExecutor继承的count, exists, findAll, findOne
 
2、demo示例

因为比较简单,这里只是简单写几个简单的方法进行测试,这里为了简单方便起见,我讲测试的方法放到Controller内——Customercontroller。
1)、save方法,初始化,保存Customer数据
@Controller
@RequestMapping("/customer")
public class CustomerController {
@Autowired
private CustomerRepository repository;

/**
* 初始化数据
*/
@RequestMapping("/index")
public void index() {
// save a couple of customers
repository.save(new Customer("Jack", "Bauer"));
repository.save(new Customer("Chloe", "O'Brian"));
repository.save(new Customer("Kim", "Bauer"));
repository.save(new Customer("David", "Palmer"));
repository.save(new Customer("Michelle", "Dessler"));
repository.save(new Customer("Bauer", "Dessler"));
}
}这里调用了接口继承的save方法。
2)、findAll方法,查找出完成初始化的数据
/**
* 查询所有
*/
@RequestMapping("/findAll")
public void findAll(){
List<Customer> result = repository.findAll();
for (Customer customer:result){
System.out.println(customer.toString());
}
System.out.println("-------------------------------------------");
}3)、findOne方法,根据long型Id为参数,返回对应的人
/**
* 查询ID为1的数据
*/
@RequestMapping("/findOne")
public void findOne(){
Customer result = repository.findOne(1L);
if(result!=null){
System.out.println(result.toString());
}
System.out.println("-------------------------------------------");
}4)、delete方法,根据Id删除指定数据,也可以用批量删除方法
/**
* 查询ID为1的数据
*/
@RequestMapping("/delete")
public void delete(){

System.out.println("删除前数据:");
List<Customer> customers = repository.findAll();
for (Customer customer:customers){
System.out.println(customer.toString());
}

System.out.println("删除ID=3数据:");
repository.delete(3l);

System.out.println("删除后数据:");
customers = repository.findAll();
for (Customer customer:customers){
System.out.println(customer.toString());
}
System.out.println("-------------------------------------------");
}5)、其他还有很多,用法方法,大同小异,只是在功能上不完全重叠。
 
 
是不是感觉有JPA真心方便,不用写mapper文件,不用各种配置,甚至不用定义基本的增删改查方法,写个类似DAO的类,继承一下JpaRepository或者CrudRepository,当程序运行的时候,会创建一个虚拟的实训类,然后执行相关逻辑。

这一节就到这里,下一节讲通过方法名来实现一些简单查询,有一定的局限性,但是可以学习一下这种操作,以备不时之需。
 
参考:
官方文档,https://docs.spring.io/spring- ... /html
PDF官方文档下载:下载地址
API官方文档:API文档

相关DEMO示例:https://github.com/icnws/spring-data-jpa-demo
 
个人博客:http://www.icnws.com 查看全部
上一节讲了spring-data-jpa的基本配置,这一节主要是讲自定义Repository继承了JpaRepository相关的一些方法,包括基本的增删改查方法。

1、方法列表

1)、从PagingAndSortingRepository继承的findAll方法
2)、从CrudRepository继承的count, delete,deleteAll, exists, findOne, save等方法
3)、从QueryByExampleExecutor继承的count, exists, findAll, findOne
 
2、demo示例

因为比较简单,这里只是简单写几个简单的方法进行测试,这里为了简单方便起见,我讲测试的方法放到Controller内——Customercontroller。
1)、save方法,初始化,保存Customer数据
@Controller
@RequestMapping("/customer")
public class CustomerController {
@Autowired
private CustomerRepository repository;

/**
* 初始化数据
*/
@RequestMapping("/index")
public void index() {
// save a couple of customers
repository.save(new Customer("Jack", "Bauer"));
repository.save(new Customer("Chloe", "O'Brian"));
repository.save(new Customer("Kim", "Bauer"));
repository.save(new Customer("David", "Palmer"));
repository.save(new Customer("Michelle", "Dessler"));
repository.save(new Customer("Bauer", "Dessler"));
}
}
这里调用了接口继承的save方法。
2)、findAll方法,查找出完成初始化的数据
    /**
* 查询所有
*/
@RequestMapping("/findAll")
public void findAll(){
List<Customer> result = repository.findAll();
for (Customer customer:result){
System.out.println(customer.toString());
}
System.out.println("-------------------------------------------");
}
3)、findOne方法,根据long型Id为参数,返回对应的人
    /**
* 查询ID为1的数据
*/
@RequestMapping("/findOne")
public void findOne(){
Customer result = repository.findOne(1L);
if(result!=null){
System.out.println(result.toString());
}
System.out.println("-------------------------------------------");
}
4)、delete方法,根据Id删除指定数据,也可以用批量删除方法
    /**
* 查询ID为1的数据
*/
@RequestMapping("/delete")
public void delete(){

System.out.println("删除前数据:");
List<Customer> customers = repository.findAll();
for (Customer customer:customers){
System.out.println(customer.toString());
}

System.out.println("删除ID=3数据:");
repository.delete(3l);

System.out.println("删除后数据:");
customers = repository.findAll();
for (Customer customer:customers){
System.out.println(customer.toString());
}
System.out.println("-------------------------------------------");
}
5)、其他还有很多,用法方法,大同小异,只是在功能上不完全重叠。
 
 
是不是感觉有JPA真心方便,不用写mapper文件,不用各种配置,甚至不用定义基本的增删改查方法,写个类似DAO的类,继承一下JpaRepository或者CrudRepository,当程序运行的时候,会创建一个虚拟的实训类,然后执行相关逻辑。

这一节就到这里,下一节讲通过方法名来实现一些简单查询,有一定的局限性,但是可以学习一下这种操作,以备不时之需。
 
参考:
官方文档,https://docs.spring.io/spring- ... /html
PDF官方文档下载:下载地址
API官方文档:API文档

相关DEMO示例:https://github.com/icnws/spring-data-jpa-demo
 
个人博客:http://www.icnws.com

Spring Data JPA系列:基本配置

wayne 发表了文章 • 0 个评论 • 523 次浏览 • 2017-06-29 10:46 • 来自相关话题

最近在看spring boot相关的内容,在看到spring data JPA的时候,发现通过程序猿DD以及泥瓦匠的博客来看,都是基础的引入JPA依赖,且版本也不是最新的。通过搜索引擎得到的结果也大多是copy by copy,没有什么实际有深度的文章,这里将通过阅读官方文档,学到的一些东西记录一下,方便别人来学习参考。
这一节讲你基础的,配置依赖:

1、在pom.xml里加入如下依赖:
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
2、当前springboot的版本信息如下:
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.5.4.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
3、创建dto对象:
package com.example.demo.dto;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;

@Entity
public class Customer {
@Id
@GeneratedValue(strategy=GenerationType.AUTO)
private Long id;
private String firstName;
private String lastName;
protected Customer() {}
public Customer(String firstName, String lastName) {
this.firstName = firstName;
this.lastName = lastName;
}
@Override
public String toString() {
return String.format(
"Customer[id=%d, firstName='%s', lastName='%s']",
id, firstName, lastName);
}
}
4、创建操作数据的Repository对象:
package com.example.demo.repositories;
import com.example.demo.dto.Customer;
import org.springframework.data.jpa.repository.JpaRepository;
public interface CustomerRepository extends JpaRepository<Customer, Long> {
}
对于JpaRepository需要知道:
public interface JpaRepository<T,ID extends Serializable>
extends PagingAndSortingRepository<T,ID>, QueryByExampleExecutor<T>

Methods inherited from interface org.springframework.data.repository.PagingAndSortingRepository
findAll
Methods inherited from interface org.springframework.data.repository.CrudRepository
count, delete, delete, delete, deleteAll, exists, findOne, save
Methods inherited from interface org.springframework.data.repository.query.QueryByExampleExecutor
count, exists, findAll, findOne
从上面可以看出,CustomerRepository继承了JpaRepository之后就拥有了CrudRepository、QueryByExampleExecutor、PagingAndSortingRepository的基本能力了,包括基本的增删改查都有了。
5、数据库配置
需要在application.properties加上如下参数:
spring.datasource.url=jdbc:mysql://localhost:3306/test
spring.datasource.username=root
spring.datasource.password=123456
spring.datasource.driver-class-name=com.mysql.jdbc.Driver
spring.jpa.properties.hibernate.hbm2ddl.auto=create
这里要注意到最后一行参数spring.jpa.properties.hibernate.hbm2ddl.auto=create,这个参数是为了方便处理数据库表,该属性的值一般有如下几个:
validate 加载hibernate时,验证创建数据库表结构
create 每次加载hibernate,重新创建数据库表结构,这就是导致数据库表数据丢失的原因。
create-drop 加载hibernate时创建,退出是删除表结构
update 加载hibernate自动更新数据库结构
很多教程上写create-drop,就是每次启动都要重新创建表,如果表比较少还好,但是多了就是麻烦,还有初始化数据,我这里写的是create,只完成表的初始化创建即可,后面就不需要关注了。
建议是,在测试环境慎重使用,在生产环境坚决不使用,要不然,死都不知道怎么死的。

到此,基本的配置已经完成了,下一篇文章会写如何使用,如果对你有帮助希望你也分享给你的小伙伴吧。

参考:
官方文档,https://docs.spring.io/spring- ... /html
PDF官方文档下载:下载地址
API官方文档:API文档

相关DEMO示例:https://github.com/icnws/spring-data-jpa-demo 查看全部
最近在看spring boot相关的内容,在看到spring data JPA的时候,发现通过程序猿DD以及泥瓦匠的博客来看,都是基础的引入JPA依赖,且版本也不是最新的。通过搜索引擎得到的结果也大多是copy by copy,没有什么实际有深度的文章,这里将通过阅读官方文档,学到的一些东西记录一下,方便别人来学习参考。
这一节讲你基础的,配置依赖:

1、在pom.xml里加入如下依赖:
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>

2、当前springboot的版本信息如下:
    <parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.5.4.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>

3、创建dto对象:
package com.example.demo.dto;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;

@Entity
public class Customer {
@Id
@GeneratedValue(strategy=GenerationType.AUTO)
private Long id;
private String firstName;
private String lastName;
protected Customer() {}
public Customer(String firstName, String lastName) {
this.firstName = firstName;
this.lastName = lastName;
}
@Override
public String toString() {
return String.format(
"Customer[id=%d, firstName='%s', lastName='%s']",
id, firstName, lastName);
}
}

4、创建操作数据的Repository对象:
package com.example.demo.repositories;
import com.example.demo.dto.Customer;
import org.springframework.data.jpa.repository.JpaRepository;
public interface CustomerRepository extends JpaRepository<Customer, Long> {
}

对于JpaRepository需要知道:
public interface JpaRepository<T,ID extends Serializable>
extends PagingAndSortingRepository<T,ID>, QueryByExampleExecutor<T>

Methods inherited from interface org.springframework.data.repository.PagingAndSortingRepository
findAll
Methods inherited from interface org.springframework.data.repository.CrudRepository
count, delete, delete, delete, deleteAll, exists, findOne, save
Methods inherited from interface org.springframework.data.repository.query.QueryByExampleExecutor
count, exists, findAll, findOne

从上面可以看出,CustomerRepository继承了JpaRepository之后就拥有了CrudRepository、QueryByExampleExecutor、PagingAndSortingRepository的基本能力了,包括基本的增删改查都有了。
5、数据库配置
需要在application.properties加上如下参数:
spring.datasource.url=jdbc:mysql://localhost:3306/test
spring.datasource.username=root
spring.datasource.password=123456
spring.datasource.driver-class-name=com.mysql.jdbc.Driver
spring.jpa.properties.hibernate.hbm2ddl.auto=create

这里要注意到最后一行参数spring.jpa.properties.hibernate.hbm2ddl.auto=create,这个参数是为了方便处理数据库表,该属性的值一般有如下几个:
validate               加载hibernate时,验证创建数据库表结构
create 每次加载hibernate,重新创建数据库表结构,这就是导致数据库表数据丢失的原因。
create-drop 加载hibernate时创建,退出是删除表结构
update 加载hibernate自动更新数据库结构

很多教程上写create-drop,就是每次启动都要重新创建表,如果表比较少还好,但是多了就是麻烦,还有初始化数据,我这里写的是create,只完成表的初始化创建即可,后面就不需要关注了。
建议是,在测试环境慎重使用,在生产环境坚决不使用,要不然,死都不知道怎么死的。

到此,基本的配置已经完成了,下一篇文章会写如何使用,如果对你有帮助希望你也分享给你的小伙伴吧。

参考:
官方文档,https://docs.spring.io/spring- ... /html
PDF官方文档下载:下载地址
API官方文档:API文档

相关DEMO示例:https://github.com/icnws/spring-data-jpa-demo

SpringDataJpa如何切换多数据源

泥瓦匠BYSocket 回复了问题 • 3 人关注 • 2 个回复 • 259 次浏览 • 2017-06-26 18:19 • 来自相关话题

spring中的懒加载与事务--排坑记录

下一秒升华 发表了文章 • 0 个评论 • 235 次浏览 • 2017-06-23 14:02 • 来自相关话题

一 案例描述
本文主要描述了开发中常见的几个与spring懒加载和事务相关的案例,主要描述常见的使用场景,以及如何规避他们,给出具体的代码。 
1. 在新的线程中,访问某个持久化对象的懒加载属性。 
2. 在quartz定时任务中,访问某个持久化对象的懒加载属性。 
3. 在dubbo,motan一类rpc框架中,远程调用时服务端session关闭的问题。 
 
上面三个案例,其实核心都是一个问题,就是牵扯到spring对事务的管理,而懒加载这个技术,只是比较容易体现出事务出错的一个实践,主要用它来引发问题,进而对问题进行思考。 
 
二 前期准备 
为了能直观的暴露出第一个案例的问题,我新建了一个项目,采用传统的mvc分层,一个student.java实体类,一个studentDao.java持久层,一个studentService.java业务层,一个studentController控制层。 
@Entity
@Table(name = "student")
public class Student {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private Integer id;
private String name;

getter..setter..
}
持久层使用springdata,框架自动扩展出CURD方法 
public interface StudentDao extends JpaRepository<Student, Integer>{
}
service层,先给出普通的调用方法。用于错误演示。
@Service
public class StudentService {

@Autowired
StudentDao studentDao;

public void testNormalGetOne(){
Student student = studentDao.getOne(1);
System.out.println(student.getName());
}
}
注意:getOne和findOne都是springdata提供的根据id查找单个实体的方法,区别是前者是懒加载,后者是立即加载。我们使用getOne来进行懒加载的实验,就不用大费周章去写懒加载属性,设置多个实体类了。
 
controller层,不是简简单单的调用,而是在新的线程中调用。使用controller层来代替单元测试(实际项目中,通常使用controller调用service,然后在浏览器或者http工具中调用触发,较为方便)
@RequestMapping("/testNormalGetOne")
@ResponseBody
public String testNormalGetOne() {
new Thread(new Runnable() {
@Override
public void run() {
studentService.testNormalGetOne();
}
}).start();
return "testNormalGetOne";
}
 启动项目后,访问`localhost:8080/testNormalGetOne`报错如下: 
Exception in thread "Thread-6" org.hibernate.LazyInitializationException: could not initialize proxy - no Session
at org.hibernate.proxy.AbstractLazyInitializer.initialize(AbstractLazyInitializer.java:148)
at org.hibernate.proxy.AbstractLazyInitializer.getImplementation(AbstractLazyInitializer.java:266)
at org.hibernate.proxy.pojo.javassist.JavassistLazyInitializer.invoke(JavassistLazyInitializer.java:68)
at com.example.transaction.entity.Student_$$_jvste17_0.getName(Student_$$_jvste17_0.java)
at com.example.transaction.service.StudentService.testNormalGetOne(StudentService.java:71)
at com.example.transaction.service.StudentService$$FastClassBySpringCGLIB$$f8048714.invoke(<generated>)
at org.springframework.cglib.proxy.MethodProxy.invoke(MethodProxy.java:204)
at org.springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor.intercept(CglibAopProxy.java:651)
at com.example.transaction.service.StudentService$$EnhancerBySpringCGLIB$$a6640151.testNormalGetOne(<generated>)
at com.example.transaction.controller.StudentController$1.run(StudentController.java:71)
at java.lang.Thread.run(Thread.java:745)
三 问题分析
no session说明了什么? 
道理很简单,因为spring的session是和线程绑定的,在整个model->dao->service->controller的调用链中,这种事务和线程绑定的机制非常契合。而我们出现的问题正式由于新开启了一个线程,这个线程与调用链的线程不是同一个。 
 
四 问题解决
 我们先使用一种不太优雅的方式解决这个问题。在新的线程中,手动打开session。 
public void testNormalGetOne() {
EntityManagerFactory entityManagerFactory = ApplicationContextProvider.getApplicationContext().getBean(EntityManagerFactory.class);
EntityManager entityManager = entityManagerFactory.createEntityManager();
EntityManagerHolder entityManagerHolder = new EntityManagerHolder(entityManager);
TransactionSynchronizationManager.bindResource(entityManagerFactory, entityManagerHolder);
Student student = studentDao.getOne(1);
System.out.println(student.getName());
TransactionSynchronizationManager.unbindResource(entityManagerFactory);
EntityManagerFactoryUtils.closeEntityManager(entityManager);
}
由于我们使用了JPA,所以事务是由EntityManagerFactory这个工厂类生成的EntityManager来管理的。TransactionSynchronizationManager.bindResource(entityManagerFactory, entityManagerHolder);这个方法使用事务管理器绑定session。 而ApplicationContextProvider这个工具类是用来获取spring容器中的EntityManagerFactory的,为什么不用注入的方式,下文讲解。它的代码如下:
public class ApplicationContextProvider implements ApplicationContextAware {

private static ApplicationContext context = null;

public static ApplicationContext getApplicationContext() {
return context;
}

@Override
public void setApplicationContext(ApplicationContext ac) throws BeansException {
context = ac;
}
}
问题暂时得到了解决。
 
五 问题再思考
我们一般情况下使用懒加载属性,为什么没有出现no session的问题呢?相信大家都知道`@Transactional`这个注解,他会帮我们进行事务包裹,当然也会绑定session;以及大家熟知的hiberbate中的`OpenSessionInterceptor`和`OpenSessionInViewFilter`以及jpa中的` OpenEntityManagerInViewInterceptor`都是在没有session的情况下,打开session的过滤器。这种方法开始前依赖事务开启,方法结束后回收资源的操作,非常适合用过滤器拦截器处理,后续的两个未讲解的案例,其实都是使用了特殊的过滤器。 
 
看一下官方文档如何描述这个jpa中的过滤器的: 

29.3.4 Open EntityManager in View 
 
If you are running a web application, Spring Boot will by default register OpenEntityManagerInViewInterceptor to apply the "Open EntityManager in View" pattern, i.e. to allow for lazy loading in web views. If you don’t want this behavior you should set spring.jpa.open-in-view to false in your application.properties. 

我们尝试着关闭这个过滤器: 配置application.properties/application.yml文件
spring.jpa.open-in-view=false
再使用正常的方式访问懒加载属性(而不是在一个新的线程中): 
@RequestMapping("/testNormalGetOne")
@ResponseBody
public String testNormalGetOne() {
// new Thread(new Runnable() {
// @Override
// public void run() {
studentService.testNormalGetOne();
// }
// }).start();
return "testNormalGetOne";
}
报错如下: 
{
"timestamp": 1498194914012,
"status": 500,
"error": "Internal Server Error",
"exception": "org.hibernate.LazyInitializationException",
"message": "could not initialize proxy - no Session",
"path": "/testNormalGetOne"
}
 是的,我们使用spring的controller作为单元测试时,以及我们平时在直接使用jpa的懒加载属性时没有太关注这个jpa的特性,因为springboot帮我们默认开启了这个过滤器。这也解释了,为什么在新的线程中,定时任务线程中,rpc远程调用时session没有打开的原因,因为这些流程没有经过springboot的web调用链。 
 
六 另外两个实战案例 
上文已经阐释了,为什么quartz定时任务中访问懒加载属性,rpc框架服务端访问懒加载属性(注意不是客户端,客户端访问懒加载属性那是一种作死的行为,因为是代理对象)为出现问题。我们仿照spring打开session的思路(这取决于你使用hibernate还是jpa,抑或是mybatis),来编写我们的过滤器。 
quartz中打开session:
使用quartz提供的`JobListenerSupport`支持,编写一个任务过滤器,用于在每次任务执行时打开session 
public class OpenEntityManagerJobListener extends JobListenerSupport implements ApplicationContextAware {

@Override
public String getName() {
return "OpenEntityManagerJobListener";
}

EntityManagerFactory entityManagerFactory;

@Override
public void jobToBeExecuted(JobExecutionContext context) {
entityManagerFactory = applicationContext.getBean(EntityManagerFactory.class);
EntityManager entityManager = entityManagerFactory.createEntityManager();
EntityManagerHolder emHolder = new EntityManagerHolder(entityManager);
TransactionSynchronizationManager.bindResource(entityManagerFactory, emHolder);
}

@Override
public void jobWasExecuted(JobExecutionContext context, JobExecutionException jobException) {
EntityManagerHolder emHolder = (EntityManagerHolder) TransactionSynchronizationManager.unbindResource(entityManagerFactory);
EntityManagerFactoryUtils.closeEntityManager(emHolder.getEntityManager());
}


ApplicationContext applicationContext;

@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
this.applicationContext = applicationContext;
if(this.applicationContext ==null) throw new RuntimeException("applicationContext is null");
}
}
配置调度工厂:
//调度工厂
@Bean
public SchedulerFactoryBean schedulerFactoryBean() {
SchedulerFactoryBean factoryBean = new SchedulerFactoryBean();
factoryBean.setTriggers(triggerFactoryBeans().getObject());
factoryBean.setGlobalJobListeners(openEntityManagerJobListener());
return factoryBean;
}
也可以参考我的另一篇描述更为细致的文章,那是我还是刚刚参加工作,可能有些许疏漏之处,不过参考是够了。传送门:解决Quartz定时器中查询懒加载数据no session的问题 
 
Motan(我现在使用的rpc框架)服务端打开session 
利用了motan对spi扩展的支持,编写了一个Filter,主要参考了motan的spi过滤器写法和springdata打开session/entityManager的思路。 
 
@SpiMeta(name = "openjpasession")
@Activation(sequence = 100)
public class OpenEntityManagerInMotanFilter implements Filter {
private Logger logger = LoggerFactory.getLogger(OpenEntityManagerInMotanFilter.class);

/**
* Default EntityManagerFactory bean name: "entityManagerFactory".
* Only applies when no "persistenceUnitName" param has been specified.
*
* @see #setEntityManagerFactoryBeanName
* @see #setPersistenceUnitName
*/
public static final String DEFAULT_ENTITY_MANAGER_FACTORY_BEAN_NAME = "entityManagerFactory";


private String entityManagerFactoryBeanName;

private String persistenceUnitName;

private volatile EntityManagerFactory entityManagerFactory;


/**
* Set the bean name of the EntityManagerFactory to fetch from Spring's
* root application context.
* <p>Default is "entityManagerFactory". Note that this default only applies
* when no "persistenceUnitName" param has been specified.
*
* @see #setPersistenceUnitName
* @see #DEFAULT_ENTITY_MANAGER_FACTORY_BEAN_NAME
*/
public void setEntityManagerFactoryBeanName(String entityManagerFactoryBeanName) {
this.entityManagerFactoryBeanName = entityManagerFactoryBeanName;
}

/**
* Return the bean name of the EntityManagerFactory to fetch from Spring's
* root application context.
*/
protected String getEntityManagerFactoryBeanName() {
return this.entityManagerFactoryBeanName;
}

/**
* Set the name of the persistence unit to access the EntityManagerFactory for.
* <p>This is an alternative to specifying the EntityManagerFactory by bean name,
* resolving it by its persistence unit name instead. If no bean name and no persistence
* unit name have been specified, we'll check whether a bean exists for the default
* bean name "entityManagerFactory"; if not, a default EntityManagerFactory will
* be retrieved through finding a single unique bean of type EntityManagerFactory.
*
* @see #setEntityManagerFactoryBeanName
* @see #DEFAULT_ENTITY_MANAGER_FACTORY_BEAN_NAME
*/
public void setPersistenceUnitName(String persistenceUnitName) {
this.persistenceUnitName = persistenceUnitName;
}

/**
* Return the name of the persistence unit to access the EntityManagerFactory for, if any.
*/
protected String getPersistenceUnitName() {
return this.persistenceUnitName;
}

/**
* Look up the EntityManagerFactory that this filter should use.
* <p>The default implementation looks for a bean with the specified name
* in Spring's root application context.
*
* @return the EntityManagerFactory to use
* @see #getEntityManagerFactoryBeanName
*/
protected EntityManagerFactory lookupEntityManagerFactory() {

String emfBeanName = getEntityManagerFactoryBeanName();
String puName = getPersistenceUnitName();
if (StringUtils.hasLength(emfBeanName)) {
return ApplicationContextProvider.getApplicationContext().getBean(emfBeanName, EntityManagerFactory.class);
} else if (!StringUtils.hasLength(puName) && ApplicationContextProvider.getApplicationContext().containsBean(DEFAULT_ENTITY_MANAGER_FACTORY_BEAN_NAME)) {
return ApplicationContextProvider.getApplicationContext().getBean(DEFAULT_ENTITY_MANAGER_FACTORY_BEAN_NAME, EntityManagerFactory.class);
} else {
// Includes fallback search for single EntityManagerFactory bean by type.
return EntityManagerFactoryUtils.findEntityManagerFactory(ApplicationContextProvider.getApplicationContext(), puName);
}
}

/**
* Create a JPA EntityManager to be bound to a request.
* <p>Can be overridden in subclasses.
*
* @param emf the EntityManagerFactory to use
* @see javax.persistence.EntityManagerFactory#createEntityManager()
*/
protected EntityManager createEntityManager(EntityManagerFactory emf) {
return emf.createEntityManager();
}

@Override
public Response filter(Caller<?> caller, Request request) {
if (!(caller instanceof Provider)) {
return caller.call(request);
}

EntityManagerFactory emf = null;
try {
emf = lookupEntityManagerFactory();
} catch (Exception e) {
logger.error(e.getMessage(), e);
}

//可能没有启用openjpa
if (emf == null) {
return caller.call(request);
}

try {
//如果没有绑定,绑定到当前线程
if (TransactionSynchronizationManager.getResource(emf) == null) {
EntityManager em = createEntityManager(emf);
EntityManagerHolder emHolder = new EntityManagerHolder(em);
TransactionSynchronizationManager.bindResource(emf, emHolder);
}
} catch (Exception e) {
logger.error(e.getLocalizedMessage(), e);
}
try {
return caller.call(request);
} finally {
//解除绑定
closeManager(emf);
}
}

/**
* 关闭 emf
*
* @param emf
*/
private void closeManager(EntityManagerFactory emf) {
if (emf == null || TransactionSynchronizationManager.getResource(emf) == null) {
return;
}
EntityManagerHolder emHolder = null;
try {
emHolder = (EntityManagerHolder) TransactionSynchronizationManager.unbindResource(emf);
} catch (IllegalStateException e) {
logger.error(e.getLocalizedMessage(), e);
}
try {
if (emHolder != null) {
EntityManagerFactoryUtils.closeEntityManager(emHolder.getEntityManager());
}
} catch (Exception e) {
logger.error(e.getLocalizedMessage(), e);
}
}
}
 
七 总结 
springboot中的事务管理做的永远比我们想的多,事务管理器的使用场景,@Transactional究竟起了哪些作用,以及spring-data这个对DDD最佳的阐释,以及mybatis一类的非j2ee规范在微服务的地位中是否高于jpa,各个层次之间的实体传输,消息传递...都是值得思考的。 查看全部
一 案例描述
本文主要描述了开发中常见的几个与spring懒加载和事务相关的案例,主要描述常见的使用场景,以及如何规避他们,给出具体的代码。 
1. 在新的线程中,访问某个持久化对象的懒加载属性。 
2. 在quartz定时任务中,访问某个持久化对象的懒加载属性。 
3. 在dubbo,motan一类rpc框架中,远程调用时服务端session关闭的问题。 
 
上面三个案例,其实核心都是一个问题,就是牵扯到spring对事务的管理,而懒加载这个技术,只是比较容易体现出事务出错的一个实践,主要用它来引发问题,进而对问题进行思考。 
 
二 前期准备 
为了能直观的暴露出第一个案例的问题,我新建了一个项目,采用传统的mvc分层,一个student.java实体类,一个studentDao.java持久层,一个studentService.java业务层,一个studentController控制层。 
@Entity
@Table(name = "student")
public class Student {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private Integer id;
private String name;

getter..setter..
}

持久层使用springdata,框架自动扩展出CURD方法 
public interface StudentDao extends JpaRepository<Student, Integer>{
}

service层,先给出普通的调用方法。用于错误演示。
@Service
public class StudentService {

@Autowired
StudentDao studentDao;

public void testNormalGetOne(){
Student student = studentDao.getOne(1);
System.out.println(student.getName());
}
}

注意:getOne和findOne都是springdata提供的根据id查找单个实体的方法,区别是前者是懒加载,后者是立即加载。我们使用getOne来进行懒加载的实验,就不用大费周章去写懒加载属性,设置多个实体类了。
 
controller层,不是简简单单的调用,而是在新的线程中调用。使用controller层来代替单元测试(实际项目中,通常使用controller调用service,然后在浏览器或者http工具中调用触发,较为方便)
@RequestMapping("/testNormalGetOne")
@ResponseBody
public String testNormalGetOne() {
new Thread(new Runnable() {
@Override
public void run() {
studentService.testNormalGetOne();
}
}).start();
return "testNormalGetOne";
}

 启动项目后,访问`localhost:8080/testNormalGetOne`报错如下: 
Exception in thread "Thread-6" org.hibernate.LazyInitializationException: could not initialize proxy - no Session
at org.hibernate.proxy.AbstractLazyInitializer.initialize(AbstractLazyInitializer.java:148)
at org.hibernate.proxy.AbstractLazyInitializer.getImplementation(AbstractLazyInitializer.java:266)
at org.hibernate.proxy.pojo.javassist.JavassistLazyInitializer.invoke(JavassistLazyInitializer.java:68)
at com.example.transaction.entity.Student_$$_jvste17_0.getName(Student_$$_jvste17_0.java)
at com.example.transaction.service.StudentService.testNormalGetOne(StudentService.java:71)
at com.example.transaction.service.StudentService$$FastClassBySpringCGLIB$$f8048714.invoke(<generated>)
at org.springframework.cglib.proxy.MethodProxy.invoke(MethodProxy.java:204)
at org.springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor.intercept(CglibAopProxy.java:651)
at com.example.transaction.service.StudentService$$EnhancerBySpringCGLIB$$a6640151.testNormalGetOne(<generated>)
at com.example.transaction.controller.StudentController$1.run(StudentController.java:71)
at java.lang.Thread.run(Thread.java:745)

三 问题分析
no session说明了什么? 
道理很简单,因为spring的session是和线程绑定的,在整个model->dao->service->controller的调用链中,这种事务和线程绑定的机制非常契合。而我们出现的问题正式由于新开启了一个线程,这个线程与调用链的线程不是同一个。 
 
四 问题解决
 我们先使用一种不太优雅的方式解决这个问题。在新的线程中,手动打开session。 
public void testNormalGetOne() {
EntityManagerFactory entityManagerFactory = ApplicationContextProvider.getApplicationContext().getBean(EntityManagerFactory.class);
EntityManager entityManager = entityManagerFactory.createEntityManager();
EntityManagerHolder entityManagerHolder = new EntityManagerHolder(entityManager);
TransactionSynchronizationManager.bindResource(entityManagerFactory, entityManagerHolder);
Student student = studentDao.getOne(1);
System.out.println(student.getName());
TransactionSynchronizationManager.unbindResource(entityManagerFactory);
EntityManagerFactoryUtils.closeEntityManager(entityManager);
}

由于我们使用了JPA,所以事务是由EntityManagerFactory这个工厂类生成的EntityManager来管理的。TransactionSynchronizationManager.bindResource(entityManagerFactory, entityManagerHolder);这个方法使用事务管理器绑定session。 而ApplicationContextProvider这个工具类是用来获取spring容器中的EntityManagerFactory的,为什么不用注入的方式,下文讲解。它的代码如下:
public class ApplicationContextProvider implements ApplicationContextAware {

private static ApplicationContext context = null;

public static ApplicationContext getApplicationContext() {
return context;
}

@Override
public void setApplicationContext(ApplicationContext ac) throws BeansException {
context = ac;
}
}

问题暂时得到了解决。
 
五 问题再思考
我们一般情况下使用懒加载属性,为什么没有出现no session的问题呢?相信大家都知道`@Transactional`这个注解,他会帮我们进行事务包裹,当然也会绑定session;以及大家熟知的hiberbate中的`OpenSessionInterceptor`和`OpenSessionInViewFilter`以及jpa中的` OpenEntityManagerInViewInterceptor`都是在没有session的情况下,打开session的过滤器。这种方法开始前依赖事务开启,方法结束后回收资源的操作,非常适合用过滤器拦截器处理,后续的两个未讲解的案例,其实都是使用了特殊的过滤器。 
 
看一下官方文档如何描述这个jpa中的过滤器的: 


29.3.4 Open EntityManager in View 
 
If you are running a web application, Spring Boot will by default register OpenEntityManagerInViewInterceptor to apply the "Open EntityManager in View" pattern, i.e. to allow for lazy loading in web views. If you don’t want this behavior you should set spring.jpa.open-in-view to false in your application.properties. 


我们尝试着关闭这个过滤器: 配置application.properties/application.yml文件
spring.jpa.open-in-view=false

再使用正常的方式访问懒加载属性(而不是在一个新的线程中): 
@RequestMapping("/testNormalGetOne")
@ResponseBody
public String testNormalGetOne() {
// new Thread(new Runnable() {
// @Override
// public void run() {
studentService.testNormalGetOne();
// }
// }).start();
return "testNormalGetOne";
}

报错如下: 
{
"timestamp": 1498194914012,
"status": 500,
"error": "Internal Server Error",
"exception": "org.hibernate.LazyInitializationException",
"message": "could not initialize proxy - no Session",
"path": "/testNormalGetOne"
}

 是的,我们使用spring的controller作为单元测试时,以及我们平时在直接使用jpa的懒加载属性时没有太关注这个jpa的特性,因为springboot帮我们默认开启了这个过滤器。这也解释了,为什么在新的线程中,定时任务线程中,rpc远程调用时session没有打开的原因,因为这些流程没有经过springboot的web调用链。 
 
六 另外两个实战案例 
上文已经阐释了,为什么quartz定时任务中访问懒加载属性,rpc框架服务端访问懒加载属性(注意不是客户端,客户端访问懒加载属性那是一种作死的行为,因为是代理对象)为出现问题。我们仿照spring打开session的思路(这取决于你使用hibernate还是jpa,抑或是mybatis),来编写我们的过滤器。 
quartz中打开session:
使用quartz提供的`JobListenerSupport`支持,编写一个任务过滤器,用于在每次任务执行时打开session 
public class OpenEntityManagerJobListener extends JobListenerSupport implements ApplicationContextAware {

@Override
public String getName() {
return "OpenEntityManagerJobListener";
}

EntityManagerFactory entityManagerFactory;

@Override
public void jobToBeExecuted(JobExecutionContext context) {
entityManagerFactory = applicationContext.getBean(EntityManagerFactory.class);
EntityManager entityManager = entityManagerFactory.createEntityManager();
EntityManagerHolder emHolder = new EntityManagerHolder(entityManager);
TransactionSynchronizationManager.bindResource(entityManagerFactory, emHolder);
}

@Override
public void jobWasExecuted(JobExecutionContext context, JobExecutionException jobException) {
EntityManagerHolder emHolder = (EntityManagerHolder) TransactionSynchronizationManager.unbindResource(entityManagerFactory);
EntityManagerFactoryUtils.closeEntityManager(emHolder.getEntityManager());
}


ApplicationContext applicationContext;

@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
this.applicationContext = applicationContext;
if(this.applicationContext ==null) throw new RuntimeException("applicationContext is null");
}
}

配置调度工厂:
//调度工厂
@Bean
public SchedulerFactoryBean schedulerFactoryBean() {
SchedulerFactoryBean factoryBean = new SchedulerFactoryBean();
factoryBean.setTriggers(triggerFactoryBeans().getObject());
factoryBean.setGlobalJobListeners(openEntityManagerJobListener());
return factoryBean;
}

也可以参考我的另一篇描述更为细致的文章,那是我还是刚刚参加工作,可能有些许疏漏之处,不过参考是够了。传送门:解决Quartz定时器中查询懒加载数据no session的问题 
 
Motan(我现在使用的rpc框架)服务端打开session 
利用了motan对spi扩展的支持,编写了一个Filter,主要参考了motan的spi过滤器写法和springdata打开session/entityManager的思路。 
 
@SpiMeta(name = "openjpasession")
@Activation(sequence = 100)
public class OpenEntityManagerInMotanFilter implements Filter {
private Logger logger = LoggerFactory.getLogger(OpenEntityManagerInMotanFilter.class);

/**
* Default EntityManagerFactory bean name: "entityManagerFactory".
* Only applies when no "persistenceUnitName" param has been specified.
*
* @see #setEntityManagerFactoryBeanName
* @see #setPersistenceUnitName
*/
public static final String DEFAULT_ENTITY_MANAGER_FACTORY_BEAN_NAME = "entityManagerFactory";


private String entityManagerFactoryBeanName;

private String persistenceUnitName;

private volatile EntityManagerFactory entityManagerFactory;


/**
* Set the bean name of the EntityManagerFactory to fetch from Spring's
* root application context.
* <p>Default is "entityManagerFactory". Note that this default only applies
* when no "persistenceUnitName" param has been specified.
*
* @see #setPersistenceUnitName
* @see #DEFAULT_ENTITY_MANAGER_FACTORY_BEAN_NAME
*/
public void setEntityManagerFactoryBeanName(String entityManagerFactoryBeanName) {
this.entityManagerFactoryBeanName = entityManagerFactoryBeanName;
}

/**
* Return the bean name of the EntityManagerFactory to fetch from Spring's
* root application context.
*/
protected String getEntityManagerFactoryBeanName() {
return this.entityManagerFactoryBeanName;
}

/**
* Set the name of the persistence unit to access the EntityManagerFactory for.
* <p>This is an alternative to specifying the EntityManagerFactory by bean name,
* resolving it by its persistence unit name instead. If no bean name and no persistence
* unit name have been specified, we'll check whether a bean exists for the default
* bean name "entityManagerFactory"; if not, a default EntityManagerFactory will
* be retrieved through finding a single unique bean of type EntityManagerFactory.
*
* @see #setEntityManagerFactoryBeanName
* @see #DEFAULT_ENTITY_MANAGER_FACTORY_BEAN_NAME
*/
public void setPersistenceUnitName(String persistenceUnitName) {
this.persistenceUnitName = persistenceUnitName;
}

/**
* Return the name of the persistence unit to access the EntityManagerFactory for, if any.
*/
protected String getPersistenceUnitName() {
return this.persistenceUnitName;
}

/**
* Look up the EntityManagerFactory that this filter should use.
* <p>The default implementation looks for a bean with the specified name
* in Spring's root application context.
*
* @return the EntityManagerFactory to use
* @see #getEntityManagerFactoryBeanName
*/
protected EntityManagerFactory lookupEntityManagerFactory() {

String emfBeanName = getEntityManagerFactoryBeanName();
String puName = getPersistenceUnitName();
if (StringUtils.hasLength(emfBeanName)) {
return ApplicationContextProvider.getApplicationContext().getBean(emfBeanName, EntityManagerFactory.class);
} else if (!StringUtils.hasLength(puName) && ApplicationContextProvider.getApplicationContext().containsBean(DEFAULT_ENTITY_MANAGER_FACTORY_BEAN_NAME)) {
return ApplicationContextProvider.getApplicationContext().getBean(DEFAULT_ENTITY_MANAGER_FACTORY_BEAN_NAME, EntityManagerFactory.class);
} else {
// Includes fallback search for single EntityManagerFactory bean by type.
return EntityManagerFactoryUtils.findEntityManagerFactory(ApplicationContextProvider.getApplicationContext(), puName);
}
}

/**
* Create a JPA EntityManager to be bound to a request.
* <p>Can be overridden in subclasses.
*
* @param emf the EntityManagerFactory to use
* @see javax.persistence.EntityManagerFactory#createEntityManager()
*/
protected EntityManager createEntityManager(EntityManagerFactory emf) {
return emf.createEntityManager();
}

@Override
public Response filter(Caller<?> caller, Request request) {
if (!(caller instanceof Provider)) {
return caller.call(request);
}

EntityManagerFactory emf = null;
try {
emf = lookupEntityManagerFactory();
} catch (Exception e) {
logger.error(e.getMessage(), e);
}

//可能没有启用openjpa
if (emf == null) {
return caller.call(request);
}

try {
//如果没有绑定,绑定到当前线程
if (TransactionSynchronizationManager.getResource(emf) == null) {
EntityManager em = createEntityManager(emf);
EntityManagerHolder emHolder = new EntityManagerHolder(em);
TransactionSynchronizationManager.bindResource(emf, emHolder);
}
} catch (Exception e) {
logger.error(e.getLocalizedMessage(), e);
}
try {
return caller.call(request);
} finally {
//解除绑定
closeManager(emf);
}
}

/**
* 关闭 emf
*
* @param emf
*/
private void closeManager(EntityManagerFactory emf) {
if (emf == null || TransactionSynchronizationManager.getResource(emf) == null) {
return;
}
EntityManagerHolder emHolder = null;
try {
emHolder = (EntityManagerHolder) TransactionSynchronizationManager.unbindResource(emf);
} catch (IllegalStateException e) {
logger.error(e.getLocalizedMessage(), e);
}
try {
if (emHolder != null) {
EntityManagerFactoryUtils.closeEntityManager(emHolder.getEntityManager());
}
} catch (Exception e) {
logger.error(e.getLocalizedMessage(), e);
}
}
}

 
七 总结 
springboot中的事务管理做的永远比我们想的多,事务管理器的使用场景,@Transactional究竟起了哪些作用,以及spring-data这个对DDD最佳的阐释,以及mybatis一类的非j2ee规范在微服务的地位中是否高于jpa,各个层次之间的实体传输,消息传递...都是值得思考的。

深入浅出 spring-data-elasticsearch - 实战案例详解(四)

泥瓦匠BYSocket 发表了文章 • 5 个评论 • 1077 次浏览 • 2017-06-22 19:14 • 来自相关话题

『  热烈的爱情到订婚早已是定点,婚一结一切了结。现在订了婚,彼此间还留着情感发展的余地,这是桩好事。- 《我们仨》 』

运行环境:JDK 7 或 8,Maven 3.0+
技术栈:SpringBoot 1.5+, Spring Data Elasticsearch 1.5+ ,ElasticSearch 2.3.2

本文提纲
一、搜索实战场景需求
二、运行 spring-data-elasticsearch-query 工程
三、spring-data-elasticsearch-query 工程代码详解
 
一、搜索实战场景需求
搜索的场景会很多,常用的搜索场景,需要搜索的字段很多,但每个字段匹配到后所占的权重又不同。比如电商网站的搜索,搜到商品名称和商品描述,自然商品名称的权重远远大于商品描述。而且单词匹配肯定不如短语匹配。这样就出现了新的需求,如何确定这些短语,即自然分词。那就利用分词器,即可得到所需要的短语,然后进行搜索。
下面介绍短语如何进行按权重分匹配搜索。
 
二、运行 spring-data-elasticsearch-query 工程
1. 后台起守护线程启动 Elasticsearchcd elasticsearch-2.3.2/
./bin/elasticsearch -d
 
git clone 下载工程 springboot-elasticsearch ,项目地址见 GitHub - https://github.com/JeffLi1993/ ... ample。
下面开始运行工程步骤(Quick Start):
2. 项目结构介绍org.spring.springboot.controller - Controller 层
org.spring.springboot.repository - ES 数据操作层
org.spring.springboot.domain - 实体类
org.spring.springboot.service - ES 业务逻辑层
Application - 应用启动类
application.properties - 应用配置文件,应用启动会自动读取配置
本地启动的 ES ,就不需要改配置文件了。如果连测试 ES 服务地址,需要修改相应配置
 
3.编译工程
在项目根目录 spring-data-elasticsearch-query,运行 maven 指令:mvn clean install

4.运行工程
右键运行 Application 应用启动类(位置:org/spring/springboot/Application.java)的 main 函数,这样就成功启动了 spring-data-elasticsearch-query 案例。
用 Postman 工具新增两个城市
 
a. 新增城市信息POST http://127.0.0.1:8080/api/city
{
"id”:"1",
"score":"5",
"name":"上海",
"description":"上海是个热城市"
}
POST http://127.0.0.1:8080/api/city
{
"id":"2",
"score”:"4",
"name”:”温岭",
"description":”温岭是个沿海城市"
}
 
下面是实战搜索语句的接口:
GET http://localhost:8080/api/city ... nt%3D城市
获取返回结果:
返回 JSON 如下:[
{
"id": 2,
"name": "温岭",
"description": "温岭是个沿海城市",
"score": 4
},
{
"id": 1,
"name": "上海",
"description": "上海是个好城市",
"score": 3
}
]
 
应用的控制台中,日志打印出查询语句的 DSL : DSL =
{
"function_score" : {
"functions" : [ {
"filter" : {
"match" : {
"name" : {
"query" : "城市",
"type" : "phrase"
}
}
},
"weight" : 1000.0
}, {
"filter" : {
"match" : {
"description" : {
"query" : "城市",
"type" : "phrase"
}
}
},
"weight" : 500.0
} ],
"score_mode" : "sum",
"min_score" : 10.0
}
}
 
三、spring-data-elasticsearch-query 工程代码详解
具体代码见 GitHub - https://github.com/JeffLi1993/springboot-learning-example 

1.pom.xml 依赖<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/ma ... gt%3B
<modelVersion>4.0.0</modelVersion>
<groupId>springboot</groupId>
<artifactId>spring-data-elasticsearch-crud</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>spring-data-elasticsearch-crud :: spring-data-elasticsearch - 基本案例 </name>
<!-- Spring Boot 启动父依赖 -->
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.5.1.RELEASE</version>
</parent>
<dependencies>
<!-- Spring Boot Elasticsearch 依赖 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-elasticsearch</artifactId>
</dependency>
<!-- Spring Boot Web 依赖 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- Junit -->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
</dependency>
</dependencies>
</project>
这里依赖的 spring-boot-starter-data-elasticsearch 版本是 1.5.1.RELEASE,对应的 spring-data-elasticsearch 版本是 2.1.0.RELEASE。对应官方文档:http://docs.spring.io/spring-d ... html/。后面数据操作层都是通过该 spring-data-elasticsearch 提供的接口实现。

2. application.properties 配置 ES 地址# ES
spring.data.elasticsearch.repositories.enabled = true
spring.data.elasticsearch.cluster-nodes = 127.0.0.1:9300
默认 9300 是 Java 客户端的端口。9200 是支持 Restful HTTP 的接口。
更多配置:
 spring.data.elasticsearch.cluster-name Elasticsearch    集群名。(默认值: elasticsearch)
 spring.data.elasticsearch.cluster-nodes    集群节点地址列表,用逗号分隔。如果没有指定,就启动一个客户端节点。
 spring.data.elasticsearch.propertie     用来配置客户端的额外属性。
 spring.data.elasticsearch.repositories.enabled     开启 Elasticsearch 仓库。(默认值:true。)
 

3. ES 数据操作层/**
* ES 操作类
* <p>
* Created by bysocket on 17/05/2017.
*/
public interface CityRepository extends ElasticsearchRepository<City, Long> {
}
接口只要继承 ElasticsearchRepository 接口类即可,具体使用的是该接口的方法: Iterable<T> search(QueryBuilder query);
Page<T> search(QueryBuilder query, Pageable pageable);
Page<T> search(SearchQuery searchQuery);
Page<T> searchSimilar(T entity, String[] fields, Pageable pageable);

4. 实体类/**
* 城市实体类
* <p>
* Created by bysocket on 03/05/2017.
*/
@Document(indexName = "province", type = "city")
public class City implements Serializable {
private static final long serialVersionUID = -1L;
/**
* 城市编号
*/
private Long id;
/**
* 城市名称
*/
private String name;
/**
* 描述
*/
private String description;
/**
* 城市评分
*/
private Integer score;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getDescription() {
return description;
}
public void setDescription(String description) {
this.description = description;
}
public Integer getScore() {
return score;
}
public void setScore(Integer score) {
this.score = score;
}
}
注意
a. City 属性名不支持驼峰式。
b. indexName 配置必须是全部小写,不然会出异常。
org.elasticsearch.indices.InvalidIndexNameException: Invalid index name [provinceIndex], must be lowercase
 
 
5. 城市 ES 业务逻辑实现类
代码如下:/**
* 城市 ES 业务逻辑实现类
* <p>
* Created by bysocket on 20/06/2017.
*/
@Service
public class CityESServiceImpl implements CityService {
private static final Logger LOGGER = LoggerFactory.getLogger(CityESServiceImpl.class);
/* 分页参数 */
Integer PAGE_SIZE = 12; // 每页数量
Integer DEFAULT_PAGE_NUMBER = 0; // 默认当前页码
/* 搜索模式 */
String SCORE_MODE_SUM = "sum"; // 权重分求和模式
Float MIN_SCORE = 10.0F; // 由于无相关性的分值默认为 1 ,设置权重分最小值为 10
@Autowired
CityRepository cityRepository; // ES 操作类
public Long saveCity(City city) {
City cityResult = cityRepository.save(city);
return cityResult.getId();
}
@Override
public List<City> searchCity(Integer pageNumber, Integer pageSize, String searchContent) {
// 校验分页参数
if (pageSize == null || pageSize <= 0) {
pageSize = PAGE_SIZE;
}
if (pageNumber == null || pageNumber < DEFAULT_PAGE_NUMBER) {
pageNumber = DEFAULT_PAGE_NUMBER;
}
LOGGER.info("\n searchCity: searchContent [" + searchContent + "] \n ");
// 构建搜索查询
SearchQuery searchQuery = getCitySearchQuery(pageNumber,pageSize,searchContent);
LOGGER.info("\n searchCity: searchContent [" + searchContent + "] \n DSL = \n " + searchQuery.getQuery().toString());
Page<City> cityPage = cityRepository.search(searchQuery);
return cityPage.getContent();
}
/**
* 根据搜索词构造搜索查询语句
*
* 代码流程:
* - 权重分查询
* - 短语匹配
* - 设置权重分最小值
* - 设置分页参数
*
* @param pageNumber 当前页码
* @param pageSize 每页大小
* @param searchContent 搜索内容
* @return
*/
private SearchQuery getCitySearchQuery(Integer pageNumber, Integer pageSize,String searchContent) {
// 短语匹配到的搜索词,求和模式累加权重分
// 权重分查询 https://www.elastic.co/guide/c ... .html
// - 短语匹配 https://www.elastic.co/guide/c ... .html
// - 字段对应权重分设置,可以优化成 enum
// - 由于无相关性的分值默认为 1 ,设置权重分最小值为 10
FunctionScoreQueryBuilder functionScoreQueryBuilder = QueryBuilders.functionScoreQuery()
.add(QueryBuilders.matchPhraseQuery("name", searchContent),
ScoreFunctionBuilders.weightFactorFunction(1000))
.add(QueryBuilders.matchPhraseQuery("description", searchContent),
ScoreFunctionBuilders.weightFactorFunction(500))
.scoreMode(SCORE_MODE_SUM).setMinScore(MIN_SCORE);
// 分页参数
Pageable pageable = new PageRequest(pageNumber, pageSize);
return new NativeSearchQueryBuilder()
.withPageable(pageable)
.withQuery(functionScoreQueryBuilder).build();
}
}
可以看到该过程实现了,短语精准匹配以及匹配到根据字段权重分求和,从而实现按权重搜索查询。代码流程如下:
- 权重分查询
- 短语匹配
- 设置权重分最小值
- 设置分页参数

注意:
- 字段对应权重分设置,可以优化成 enum
- 由于无相关性的分值默认为 1 ,设置权重分最小值为 10
 
权重分查询文档:https://www.elastic.co/guide/c ... .html。
短语匹配文档: https://www.elastic.co/guide/c ... .html。
 
四、小结
Elasticsearch 还提供很多高级的搜索功能。这里提供下需要经常逛的相关网站:
Elasticsearch 中文社区 https://elasticsearch.cn/topic/elasticsearch
Elasticsearch: 权威指南-在线版 https://www.elastic.co/guide/cn/elasticsearch/guide/current/index.html 
 

摘要: 原创出处 www.bysocket.com 「泥瓦匠BYSocket 」欢迎转载,保留摘要,谢谢! 查看全部

『  热烈的爱情到订婚早已是定点,婚一结一切了结。现在订了婚,彼此间还留着情感发展的余地,这是桩好事。- 《我们仨》 』

运行环境:JDK 7 或 8,Maven 3.0+
技术栈:SpringBoot 1.5+, Spring Data Elasticsearch 1.5+ ,ElasticSearch 2.3.2

本文提纲
一、搜索实战场景需求
二、运行 spring-data-elasticsearch-query 工程
三、spring-data-elasticsearch-query 工程代码详解
 
一、搜索实战场景需求
搜索的场景会很多,常用的搜索场景,需要搜索的字段很多,但每个字段匹配到后所占的权重又不同。比如电商网站的搜索,搜到商品名称和商品描述,自然商品名称的权重远远大于商品描述。而且单词匹配肯定不如短语匹配。这样就出现了新的需求,如何确定这些短语,即自然分词。那就利用分词器,即可得到所需要的短语,然后进行搜索。
下面介绍短语如何进行按权重分匹配搜索。
 
二、运行 spring-data-elasticsearch-query 工程
1. 后台起守护线程启动 Elasticsearch
cd elasticsearch-2.3.2/
./bin/elasticsearch -d

 
git clone 下载工程 springboot-elasticsearch ,项目地址见 GitHub - https://github.com/JeffLi1993/ ... ample
下面开始运行工程步骤(Quick Start):
2. 项目结构介绍
org.spring.springboot.controller - Controller 层
org.spring.springboot.repository - ES 数据操作层
org.spring.springboot.domain - 实体类
org.spring.springboot.service - ES 业务逻辑层
Application - 应用启动类
application.properties - 应用配置文件,应用启动会自动读取配置

本地启动的 ES ,就不需要改配置文件了。如果连测试 ES 服务地址,需要修改相应配置
 
3.编译工程
在项目根目录 spring-data-elasticsearch-query,运行 maven 指令:
mvn clean install

4.运行工程
右键运行 Application 应用启动类(位置:org/spring/springboot/Application.java)的 main 函数,这样就成功启动了 spring-data-elasticsearch-query 案例。
用 Postman 工具新增两个城市
 
a. 新增城市信息
POST http://127.0.0.1:8080/api/city
{
"id”:"1",
"score":"5",
"name":"上海",
"description":"上海是个热城市"
}
POST http://127.0.0.1:8080/api/city
{
"id":"2",
"score”:"4",
"name”:”温岭",
"description":”温岭是个沿海城市"
}

 
下面是实战搜索语句的接口:
GET http://localhost:8080/api/city ... nt%3D城市
获取返回结果:
返回 JSON 如下:
[
{
"id": 2,
"name": "温岭",
"description": "温岭是个沿海城市",
"score": 4
},
{
"id": 1,
"name": "上海",
"description": "上海是个好城市",
"score": 3
}
]

 
应用的控制台中,日志打印出查询语句的 DSL :
 DSL  = 
{
"function_score" : {
"functions" : [ {
"filter" : {
"match" : {
"name" : {
"query" : "城市",
"type" : "phrase"
}
}
},
"weight" : 1000.0
}, {
"filter" : {
"match" : {
"description" : {
"query" : "城市",
"type" : "phrase"
}
}
},
"weight" : 500.0
} ],
"score_mode" : "sum",
"min_score" : 10.0
}
}

 
三、spring-data-elasticsearch-query 工程代码详解
具体代码见 GitHub - https://github.com/JeffLi1993/springboot-learning-example 

1.pom.xml 依赖
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/ma ... gt%3B
<modelVersion>4.0.0</modelVersion>
<groupId>springboot</groupId>
<artifactId>spring-data-elasticsearch-crud</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>spring-data-elasticsearch-crud :: spring-data-elasticsearch - 基本案例 </name>
<!-- Spring Boot 启动父依赖 -->
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.5.1.RELEASE</version>
</parent>
<dependencies>
<!-- Spring Boot Elasticsearch 依赖 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-elasticsearch</artifactId>
</dependency>
<!-- Spring Boot Web 依赖 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- Junit -->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
</dependency>
</dependencies>
</project>

这里依赖的 spring-boot-starter-data-elasticsearch 版本是 1.5.1.RELEASE,对应的 spring-data-elasticsearch 版本是 2.1.0.RELEASE。对应官方文档:http://docs.spring.io/spring-d ... html/。后面数据操作层都是通过该 spring-data-elasticsearch 提供的接口实现。

2. application.properties 配置 ES 地址
# ES
spring.data.elasticsearch.repositories.enabled = true
spring.data.elasticsearch.cluster-nodes = 127.0.0.1:9300

默认 9300 是 Java 客户端的端口。9200 是支持 Restful HTTP 的接口。
更多配置:
 spring.data.elasticsearch.cluster-name Elasticsearch    集群名。(默认值: elasticsearch)
 spring.data.elasticsearch.cluster-nodes    集群节点地址列表,用逗号分隔。如果没有指定,就启动一个客户端节点。
 spring.data.elasticsearch.propertie     用来配置客户端的额外属性。
 spring.data.elasticsearch.repositories.enabled     开启 Elasticsearch 仓库。(默认值:true。)
 

3. ES 数据操作层
/**
* ES 操作类
* <p>
* Created by bysocket on 17/05/2017.
*/
public interface CityRepository extends ElasticsearchRepository<City, Long> {
}

接口只要继承 ElasticsearchRepository 接口类即可,具体使用的是该接口的方法:
    Iterable<T> search(QueryBuilder query);
Page<T> search(QueryBuilder query, Pageable pageable);
Page<T> search(SearchQuery searchQuery);
Page<T> searchSimilar(T entity, String[] fields, Pageable pageable);


4. 实体类
/**
* 城市实体类
* <p>
* Created by bysocket on 03/05/2017.
*/
@Document(indexName = "province", type = "city")
public class City implements Serializable {
private static final long serialVersionUID = -1L;
/**
* 城市编号
*/
private Long id;
/**
* 城市名称
*/
private String name;
/**
* 描述
*/
private String description;
/**
* 城市评分
*/
private Integer score;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getDescription() {
return description;
}
public void setDescription(String description) {
this.description = description;
}
public Integer getScore() {
return score;
}
public void setScore(Integer score) {
this.score = score;
}
}

注意
a. City 属性名不支持驼峰式。
b. indexName 配置必须是全部小写,不然会出异常。
org.elasticsearch.indices.InvalidIndexNameException: Invalid index name [provinceIndex], must be lowercase
 
 
5. 城市 ES 业务逻辑实现类
代码如下:
/**
* 城市 ES 业务逻辑实现类
* <p>
* Created by bysocket on 20/06/2017.
*/
@Service
public class CityESServiceImpl implements CityService {
private static final Logger LOGGER = LoggerFactory.getLogger(CityESServiceImpl.class);
/* 分页参数 */
Integer PAGE_SIZE = 12; // 每页数量
Integer DEFAULT_PAGE_NUMBER = 0; // 默认当前页码
/* 搜索模式 */
String SCORE_MODE_SUM = "sum"; // 权重分求和模式
Float MIN_SCORE = 10.0F; // 由于无相关性的分值默认为 1 ,设置权重分最小值为 10
@Autowired
CityRepository cityRepository; // ES 操作类
public Long saveCity(City city) {
City cityResult = cityRepository.save(city);
return cityResult.getId();
}
@Override
public List<City> searchCity(Integer pageNumber, Integer pageSize, String searchContent) {
// 校验分页参数
if (pageSize == null || pageSize <= 0) {
pageSize = PAGE_SIZE;
}
if (pageNumber == null || pageNumber < DEFAULT_PAGE_NUMBER) {
pageNumber = DEFAULT_PAGE_NUMBER;
}
LOGGER.info("\n searchCity: searchContent [" + searchContent + "] \n ");
// 构建搜索查询
SearchQuery searchQuery = getCitySearchQuery(pageNumber,pageSize,searchContent);
LOGGER.info("\n searchCity: searchContent [" + searchContent + "] \n DSL = \n " + searchQuery.getQuery().toString());
Page<City> cityPage = cityRepository.search(searchQuery);
return cityPage.getContent();
}
/**
* 根据搜索词构造搜索查询语句
*
* 代码流程:
* - 权重分查询
* - 短语匹配
* - 设置权重分最小值
* - 设置分页参数
*
* @param pageNumber 当前页码
* @param pageSize 每页大小
* @param searchContent 搜索内容
* @return
*/
private SearchQuery getCitySearchQuery(Integer pageNumber, Integer pageSize,String searchContent) {
// 短语匹配到的搜索词,求和模式累加权重分
// 权重分查询 https://www.elastic.co/guide/c ... .html
// - 短语匹配 https://www.elastic.co/guide/c ... .html
// - 字段对应权重分设置,可以优化成 enum
// - 由于无相关性的分值默认为 1 ,设置权重分最小值为 10
FunctionScoreQueryBuilder functionScoreQueryBuilder = QueryBuilders.functionScoreQuery()
.add(QueryBuilders.matchPhraseQuery("name", searchContent),
ScoreFunctionBuilders.weightFactorFunction(1000))
.add(QueryBuilders.matchPhraseQuery("description", searchContent),
ScoreFunctionBuilders.weightFactorFunction(500))
.scoreMode(SCORE_MODE_SUM).setMinScore(MIN_SCORE);
// 分页参数
Pageable pageable = new PageRequest(pageNumber, pageSize);
return new NativeSearchQueryBuilder()
.withPageable(pageable)
.withQuery(functionScoreQueryBuilder).build();
}
}

可以看到该过程实现了,短语精准匹配以及匹配到根据字段权重分求和,从而实现按权重搜索查询。代码流程如下:
- 权重分查询
- 短语匹配
- 设置权重分最小值
- 设置分页参数

注意:
- 字段对应权重分设置,可以优化成 enum
- 由于无相关性的分值默认为 1 ,设置权重分最小值为 10
 
权重分查询文档:https://www.elastic.co/guide/c ... .html
短语匹配文档: https://www.elastic.co/guide/c ... .html
 
四、小结
Elasticsearch 还提供很多高级的搜索功能。这里提供下需要经常逛的相关网站:
Elasticsearch 中文社区 https://elasticsearch.cn/topic/elasticsearch
Elasticsearch: 权威指南-在线版 https://www.elastic.co/guide/cn/elasticsearch/guide/current/index.html 
 


摘要: 原创出处 www.bysocket.com 「泥瓦匠BYSocket 」欢迎转载,保留摘要,谢谢!


深入浅出 spring-data-elasticsearch - 基本案例详解(三)

泥瓦匠BYSocket 发表了文章 • 30 个评论 • 2281 次浏览 • 2017-06-20 11:37 • 来自相关话题

『  风云说:能分享自己职位的知识的领导是个好领导。 』
运行环境:JDK 7 或 8,Maven 3.0+
技术栈:SpringBoot 1.5+, Spring Data Elasticsearch 1.5+ ,ElasticSearch 2.3.2
本文提纲
一、spring-data-elasticsearch-crud 的工程介绍
二、运行 spring-data-elasticsearch-crud 工程
三、spring-data-elasticsearch-crud 工程代码详解

一、spring-data-elasticsearch-crud 的工程介绍
spring-data-elasticsearch-crud 的工程,介绍 Spring Data Elasticsearch 简单的 ES 操作。Spring Data Elasticsearch 可以跟 JPA 进行类比。其使用方法也很简单。

二、运行 spring-data-elasticsearch-crud 工程
注意的是这里使用的是 ElasticSearch 2.3.2。是因为版本对应关系 https://github.com/spring-projects/spring-data-elasticsearch/wiki/Spring-Data-Elasticsearch---Spring-Boot---version-matrix; 

Spring Boot Version (x)    Spring Data Elasticsearch Version (y)    Elasticsearch Version (z)
x <= 1.3.5    y <= 1.3.4    z <= 1.7.2*
x >= 1.4.x    2.0.0 <=y < 5.0.0**    2.0.0 <= z < 5.0.0**
*  - 只需要你修改下对应的 pom 文件版本号 
** - 下一个 ES 的版本会有重大的更新
 
1. 后台起守护线程启动 Elasticsearchcd elasticsearch-2.3.2/
./bin/elasticsearch -d
git clone 下载工程 springboot-elasticsearch ,项目地址见 GitHub - https://github.com/JeffLi1993/ ... ample。
下面开始运行工程步骤(Quick Start):
 
2. 项目结构介绍org.spring.springboot.controller - Controller 层
org.spring.springboot.repository - ES 数据操作层
org.spring.springboot.domain - 实体类
org.spring.springboot.service - ES 业务逻辑层
Application - 应用启动类
application.properties - 应用配置文件,应用启动会自动读取配置本地启动的 ES ,就不需要改配置文件了。如果连测试 ES 服务地址,需要修改相应配置
 
3.编译工程
在项目根目录 spring-data-elasticsearch-crud,运行 maven 指令:mvn clean install
 4.运行工程
右键运行 Application 应用启动类(位置:/springboot-learning-example/springboot-elasticsearch/src/main/java/org/spring/springboot/Application.java)的 main 函数,这样就成功启动了 springboot-elasticsearch 案例。
用 Postman 工具新增两个城市
 
a. 新增城市信息POST http://127.0.0.1:8080/api/city
{
"id”:"1",
"score":"5",
"name":"上海",
"description":"上海是个热城市"
}
POST http://127.0.0.1:8080/api/city
{
"id":"2",
"score”:"4",
"name”:”温岭",
"description":”温岭是个沿海城市"
}
可以打开 ES 可视化工具 head 插件:http://localhost:9200/_plugin/head/:
(如果不知道怎么安装,请查阅 《Elasticsearch 和插件 elasticsearch-head 安装详解》 http://www.bysocket.com/?p=1744 。)
在「数据浏览」tab,可以查阅到 ES 中数据是否被插入,插入后的数据格式如下:{
"_index": "cityindex",
"_type": "city",
"_id": "1",
"_version": 1,
"_score": 1,
"_source": {
"id":"2",
"score”:"4",
"name”:”温岭",
"description":”温岭是个沿海城市"
}
}
下面是基本查询语句的接口:
a. 普通查询,查询城市描述GET http://localhost:8080/api/city ... on%3D温岭
返回 JSON 如下:[
{
"id": 2,
"name": "温岭",
"description": "温岭是个沿海城市",
"score": 4
}
]

b. AND 语句查询GET http://localhost:8080/api/city ... on%3D温岭&score=4
返回 JSON 如下:[
{
"id": 2,
"name": "温岭",
"description": "温岭是个沿海城市",
"score": 4
}
]如果换成 score=5 ,就没有结果了。

c. OR 语句查询GET http://localhost:8080/api/city ... on%3D上海&score=4
返回 JSON 如下:[
{
"id": 2,
"name": "温岭",
"description": "温岭是个沿海城市",
"score": 4
},
{
"id": 1,
"name": "上海",
"description": "上海是个好城市",
"score": 3
}
]
d. NOT 语句查询GET http://localhost:8080/api/city ... on%3D温州
返回 JSON 如下:[
{
"id": 2,
"name": "温岭",
"description": "温岭是个沿海城市",
"score": 4
},
{
"id": 1,
"name": "上海",
"description": "上海是个好城市",
"score": 3
}
]
e. LIKE 语句查询GET http://localhost:8080/api/city ... on%3D城市
返回 JSON 如下:[
{
"id": 2,
"name": "温岭",
"description": "温岭是个沿海城市",
"score": 4
},
{
"id": 1,
"name": "上海",
"description": "上海是个好城市",
"score": 3
}
]
三、spring-data-elasticsearch-crud 工程代码详解
具体代码见 GitHub - https://github.com/JeffLi1993/springboot-learning-example 

1.pom.xml 依赖<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/ma ... gt%3B
<modelVersion>4.0.0</modelVersion>
<groupId>springboot</groupId>
<artifactId>spring-data-elasticsearch-crud</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>spring-data-elasticsearch-crud :: spring-data-elasticsearch - 基本案例 </name>
<!-- Spring Boot 启动父依赖 -->
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.5.1.RELEASE</version>
</parent>
<dependencies>
<!-- Spring Boot Elasticsearch 依赖 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-elasticsearch</artifactId>
</dependency>
<!-- Spring Boot Web 依赖 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- Junit -->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
</dependency>
</dependencies>
</project>
这里依赖的 spring-boot-starter-data-elasticsearch 版本是 1.5.1.RELEASE,对应的 spring-data-elasticsearch 版本是 2.1.0.RELEASE。对应官方文档:http://docs.spring.io/spring-d ... html/。后面数据操作层都是通过该 spring-data-elasticsearch 提供的接口实现。

2. application.properties 配置 ES 地址# ES
spring.data.elasticsearch.repositories.enabled = true
spring.data.elasticsearch.cluster-nodes = 127.0.0.1:9300
默认 9300 是 Java 客户端的端口。9200 是支持 Restful HTTP 的接口。
更多配置:
 spring.data.elasticsearch.cluster-name Elasticsearch    集群名。(默认值: elasticsearch)
 spring.data.elasticsearch.cluster-nodes    集群节点地址列表,用逗号分隔。如果没有指定,就启动一个客户端节点。
 spring.data.elasticsearch.propertie     用来配置客户端的额外属性。
 spring.data.elasticsearch.repositories.enabled     开启 Elasticsearch 仓库。(默认值:true。)

3. ES 数据操作层/**
* ES 操作类
* <p>
* Created by bysocket on 17/05/2017.
*/
public interface CityRepository extends ElasticsearchRepository<City, Long> {
/**
* AND 语句查询
*
* @param description
* @param score
* @return
*/
List<City> findByDescriptionAndScore(String description, Integer score);
/**
* OR 语句查询
*
* @param description
* @param score
* @return
*/
List<City> findByDescriptionOrScore(String description, Integer score);
/**
* 查询城市描述
*
* 等同于下面代码
* @Query("{\"bool\" : {\"must\" : {\"term\" : {\"description\" : \"?0\"}}}}")
* Page<City> findByDescription(String description, Pageable pageable);
*
* @param description
* @param page
* @return
*/
Page<City> findByDescription(String description, Pageable page);
/**
* NOT 语句查询
*
* @param description
* @param page
* @return
*/
Page<City> findByDescriptionNot(String description, Pageable page);
/**
* LIKE 语句查询
*
* @param description
* @param page
* @return
*/
Page<City> findByDescriptionLike(String description, Pageable page);
}
接口只要继承 ElasticsearchRepository 类即可。默认会提供很多实现,比如 CRUD 和搜索相关的实现。类似于 JPA 读取数据,是使用 CrudRepository 进行操作 ES 数据。支持的默认方法有: count(), findAll(), findOne(ID), delete(ID), deleteAll(), exists(ID), save(DomainObject), save(Iterable<DomainObject>)。

另外可以看出,接口的命名是遵循规范的。常用命名规则如下:
关键字     方法命名
And          findByNameAndPwd
Or             findByNameOrSex
Is              findById
Between   findByIdBetween
Like           findByNameLike
NotLike     findByNameNotLike
OrderBy    findByIdOrderByXDesc
Not           findByNameNot

4. 实体类/**
* 城市实体类
* <p>
* Created by bysocket on 03/05/2017.
*/
@Document(indexName = "province", type = "city")
public class City implements Serializable {
private static final long serialVersionUID = -1L;
/**
* 城市编号
*/
private Long id;
/**
* 城市名称
*/
private String name;
/**
* 描述
*/
private String description;
/**
* 城市评分
*/
private Integer score;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getDescription() {
return description;
}
public void setDescription(String description) {
this.description = description;
}
public Integer getScore() {
return score;
}
public void setScore(Integer score) {
this.score = score;
}
}
注意
a. City 属性名不支持驼峰式。
b. indexName 配置必须是全部小写,不然会出异常。
org.elasticsearch.indices.InvalidIndexNameException: Invalid index name [provinceIndex], must be lowercase

四、小结
预告下
下一篇《深入浅出 spring-data-elasticsearch - 实战案例详解》,会带来实战项目中涉及到的权重分 & 短语精准匹配的讲解。
 

摘要: 原创出处 www.bysocket.com 「泥瓦匠BYSocket 」欢迎转载,保留摘要,谢谢! 查看全部
『  风云说:能分享自己职位的知识的领导是个好领导。 』
运行环境:JDK 7 或 8,Maven 3.0+
技术栈:SpringBoot 1.5+, Spring Data Elasticsearch 1.5+ ,ElasticSearch 2.3.2
本文提纲
一、spring-data-elasticsearch-crud 的工程介绍
二、运行 spring-data-elasticsearch-crud 工程
三、spring-data-elasticsearch-crud 工程代码详解

一、spring-data-elasticsearch-crud 的工程介绍
spring-data-elasticsearch-crud 的工程,介绍 Spring Data Elasticsearch 简单的 ES 操作。Spring Data Elasticsearch 可以跟 JPA 进行类比。其使用方法也很简单。

二、运行 spring-data-elasticsearch-crud 工程
注意的是这里使用的是 ElasticSearch 2.3.2。是因为版本对应关系 https://github.com/spring-projects/spring-data-elasticsearch/wiki/Spring-Data-Elasticsearch---Spring-Boot---version-matrix; 

Spring Boot Version (x)    Spring Data Elasticsearch Version (y)    Elasticsearch Version (z)
x <= 1.3.5    y <= 1.3.4    z <= 1.7.2*
x >= 1.4.x    2.0.0 <=y < 5.0.0**    2.0.0 <= z < 5.0.0**
*  - 只需要你修改下对应的 pom 文件版本号 
** - 下一个 ES 的版本会有重大的更新
 
1. 后台起守护线程启动 Elasticsearch
cd elasticsearch-2.3.2/
./bin/elasticsearch -d

git clone 下载工程 springboot-elasticsearch ,项目地址见 GitHub - https://github.com/JeffLi1993/ ... ample
下面开始运行工程步骤(Quick Start):
 
2. 项目结构介绍
org.spring.springboot.controller - Controller 层
org.spring.springboot.repository - ES 数据操作层
org.spring.springboot.domain - 实体类
org.spring.springboot.service - ES 业务逻辑层
Application - 应用启动类
application.properties - 应用配置文件,应用启动会自动读取配置
本地启动的 ES ,就不需要改配置文件了。如果连测试 ES 服务地址,需要修改相应配置
 
3.编译工程
在项目根目录 spring-data-elasticsearch-crud,运行 maven 指令:
mvn clean install

 4.运行工程
右键运行 Application 应用启动类(位置:/springboot-learning-example/springboot-elasticsearch/src/main/java/org/spring/springboot/Application.java)的 main 函数,这样就成功启动了 springboot-elasticsearch 案例。
用 Postman 工具新增两个城市
 
a. 新增城市信息
POST http://127.0.0.1:8080/api/city
{
"id”:"1",
"score":"5",
"name":"上海",
"description":"上海是个热城市"
}

POST http://127.0.0.1:8080/api/city
{
"id":"2",
"score”:"4",
"name”:”温岭",
"description":”温岭是个沿海城市"
}

可以打开 ES 可视化工具 head 插件:http://localhost:9200/_plugin/head/
(如果不知道怎么安装,请查阅 《Elasticsearch 和插件 elasticsearch-head 安装详解》 http://www.bysocket.com/?p=1744 。)
在「数据浏览」tab,可以查阅到 ES 中数据是否被插入,插入后的数据格式如下:
{
"_index": "cityindex",
"_type": "city",
"_id": "1",
"_version": 1,
"_score": 1,
"_source": {
"id":"2",
"score”:"4",
"name”:”温岭",
"description":”温岭是个沿海城市"
}
}

下面是基本查询语句的接口:
a. 普通查询,查询城市描述
GET http://localhost:8080/api/city ... on%3D温岭
返回 JSON 如下:
[
{
"id": 2,
"name": "温岭",
"description": "温岭是个沿海城市",
"score": 4
}
]


b. AND 语句查询
GET http://localhost:8080/api/city ... on%3D温岭&score=4
返回 JSON 如下:
[
{
"id": 2,
"name": "温岭",
"description": "温岭是个沿海城市",
"score": 4
}
]
如果换成 score=5 ,就没有结果了。

c. OR 语句查询
GET http://localhost:8080/api/city ... on%3D上海&score=4
返回 JSON 如下:
[
{
"id": 2,
"name": "温岭",
"description": "温岭是个沿海城市",
"score": 4
},
{
"id": 1,
"name": "上海",
"description": "上海是个好城市",
"score": 3
}
]

d. NOT 语句查询
GET http://localhost:8080/api/city ... on%3D温州
返回 JSON 如下:
[
{
"id": 2,
"name": "温岭",
"description": "温岭是个沿海城市",
"score": 4
},
{
"id": 1,
"name": "上海",
"description": "上海是个好城市",
"score": 3
}
]

e. LIKE 语句查询
GET http://localhost:8080/api/city ... on%3D城市
返回 JSON 如下:
[
{
"id": 2,
"name": "温岭",
"description": "温岭是个沿海城市",
"score": 4
},
{
"id": 1,
"name": "上海",
"description": "上海是个好城市",
"score": 3
}
]

三、spring-data-elasticsearch-crud 工程代码详解
具体代码见 GitHub - https://github.com/JeffLi1993/springboot-learning-example 

1.pom.xml 依赖
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/ma ... gt%3B
<modelVersion>4.0.0</modelVersion>
<groupId>springboot</groupId>
<artifactId>spring-data-elasticsearch-crud</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>spring-data-elasticsearch-crud :: spring-data-elasticsearch - 基本案例 </name>
<!-- Spring Boot 启动父依赖 -->
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.5.1.RELEASE</version>
</parent>
<dependencies>
<!-- Spring Boot Elasticsearch 依赖 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-elasticsearch</artifactId>
</dependency>
<!-- Spring Boot Web 依赖 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- Junit -->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
</dependency>
</dependencies>
</project>

这里依赖的 spring-boot-starter-data-elasticsearch 版本是 1.5.1.RELEASE,对应的 spring-data-elasticsearch 版本是 2.1.0.RELEASE。对应官方文档:http://docs.spring.io/spring-d ... html/。后面数据操作层都是通过该 spring-data-elasticsearch 提供的接口实现。

2. application.properties 配置 ES 地址
# ES
spring.data.elasticsearch.repositories.enabled = true
spring.data.elasticsearch.cluster-nodes = 127.0.0.1:9300
默认 9300 是 Java 客户端的端口。9200 是支持 Restful HTTP 的接口。

更多配置:
 spring.data.elasticsearch.cluster-name Elasticsearch    集群名。(默认值: elasticsearch)
 spring.data.elasticsearch.cluster-nodes    集群节点地址列表,用逗号分隔。如果没有指定,就启动一个客户端节点。
 spring.data.elasticsearch.propertie     用来配置客户端的额外属性。
 spring.data.elasticsearch.repositories.enabled     开启 Elasticsearch 仓库。(默认值:true。)

3. ES 数据操作层
/**
* ES 操作类
* <p>
* Created by bysocket on 17/05/2017.
*/
public interface CityRepository extends ElasticsearchRepository<City, Long> {
/**
* AND 语句查询
*
* @param description
* @param score
* @return
*/
List<City> findByDescriptionAndScore(String description, Integer score);
/**
* OR 语句查询
*
* @param description
* @param score
* @return
*/
List<City> findByDescriptionOrScore(String description, Integer score);
/**
* 查询城市描述
*
* 等同于下面代码
* @Query("{\"bool\" : {\"must\" : {\"term\" : {\"description\" : \"?0\"}}}}")
* Page<City> findByDescription(String description, Pageable pageable);
*
* @param description
* @param page
* @return
*/
Page<City> findByDescription(String description, Pageable page);
/**
* NOT 语句查询
*
* @param description
* @param page
* @return
*/
Page<City> findByDescriptionNot(String description, Pageable page);
/**
* LIKE 语句查询
*
* @param description
* @param page
* @return
*/
Page<City> findByDescriptionLike(String description, Pageable page);
}

接口只要继承 ElasticsearchRepository 类即可。默认会提供很多实现,比如 CRUD 和搜索相关的实现。类似于 JPA 读取数据,是使用 CrudRepository 进行操作 ES 数据。支持的默认方法有: count(), findAll(), findOne(ID), delete(ID), deleteAll(), exists(ID), save(DomainObject), save(Iterable<DomainObject>)。

另外可以看出,接口的命名是遵循规范的。常用命名规则如下:
关键字     方法命名
And          findByNameAndPwd
Or             findByNameOrSex
Is              findById
Between   findByIdBetween
Like           findByNameLike
NotLike     findByNameNotLike
OrderBy    findByIdOrderByXDesc
Not           findByNameNot

4. 实体类
/**
* 城市实体类
* <p>
* Created by bysocket on 03/05/2017.
*/
@Document(indexName = "province", type = "city")
public class City implements Serializable {
private static final long serialVersionUID = -1L;
/**
* 城市编号
*/
private Long id;
/**
* 城市名称
*/
private String name;
/**
* 描述
*/
private String description;
/**
* 城市评分
*/
private Integer score;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getDescription() {
return description;
}
public void setDescription(String description) {
this.description = description;
}
public Integer getScore() {
return score;
}
public void setScore(Integer score) {
this.score = score;
}
}

注意
a. City 属性名不支持驼峰式。
b. indexName 配置必须是全部小写,不然会出异常。
org.elasticsearch.indices.InvalidIndexNameException: Invalid index name [provinceIndex], must be lowercase

四、小结
预告下
下一篇《深入浅出 spring-data-elasticsearch - 实战案例详解》,会带来实战项目中涉及到的权重分 & 短语精准匹配的讲解。
 


摘要: 原创出处 www.bysocket.com 「泥瓦匠BYSocket 」欢迎转载,保留摘要,谢谢!


Explores The Ideas Behind Spring Transaction

whthomas 发表了文章 • 0 个评论 • 387 次浏览 • 2017-06-11 17:23 • 来自相关话题

原文地址: http://www.jianshu.com/p/b81aaf6056f4
 
 
根据之前一篇文章的例子,我发现Spring Boot是自动完成事务的配置的,所以周末我特意翻了一段源码,探究了一把Spring Boot是如何完成这个自动配置的过程的。
首先在Spring Boot的autoconfigure.jar依赖中的org.springframework.boot.autoconfigure.jdbc包下有一个DataSourceTransactionManagerAutoConfiguration类。Spring启动的时候就会根据是否添加了JDBC相关的包来判断是否要加载这个配置类中的配置。[code]@Configuration
@ConditionalOnClass({ JdbcTemplate.class, PlatformTransactionManager.class })
@AutoConfigureOrder(Ordered.LOWEST_PRECEDENCE)
@EnableConfigurationProperties(DataSourceProperties.class)
public class DataSourceTransactionManagerAutoConfiguration {

@Configuration
@ConditionalOnSingleCandidate(DataSource.class)
static class DataSourceTransactionManagerConfiguration {

private final DataSource dataSource;

private final TransactionManagerCustomizers transactionManagerCustomizers;

DataSourceTransactionManagerConfiguration(DataSource dataSource,
ObjectProvider<TransactionManagerCustomizers> transactionManagerCustomizers) {
this.dataSource = dataSource;
this.transactionManagerCustomizers = transactionManagerCustomizers
.getIfAvailable();
}

@Bean
@ConditionalOnMissingBean(PlatformTransactionManager.class)
public DataSourceTransactionManager transactionManager(
DataSourceProperties properties) {
DataSourceTransactionManager transactionManager = new DataSourceTransactionManager(
this.dataSource);
if (this.transactionManagerCustomizers != null) {
this.transactionManagerCustomizers.customize(transactionManager);
}
return transactionManager;
}

}

@ConditionalOnMissingBean(AbstractTransactionManagementConfiguration.class)
@Configuration
@EnableTransactionManagement(proxyTargetClass = true)
protected static class TransactionManagementConfiguration {

}

}
[/code]
以上这个配置类(DataSourceTransactionManagerAutoConfiguration)中有做了这样两个动作:
添加DataSourceTransactionManager事务管理器实例触发@EnableTransactionManagement注解让当前项目启用事务。
这里先从第二个动作也就是触发@EnableTransactionManagement注解这个动作开始分析,看看启动的阶段Spring到底做了些什么。[code]@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import(TransactionManagementConfigurationSelector.class)
public @interface EnableTransactionManagement {

boolean proxyTargetClass() default false;

AdviceMode mode() default AdviceMode.PROXY;

int order() default Ordered.LOWEST_PRECEDENCE;

}
[/code]
从中可以看出导入了另外一个实际的配置类TransactionManagementConfigurationSelector,这个类中仅有一个方法selectImports(),它会根据注解上的配置来选择Spring AOP中的通知配置,默认情况下是选择PROXY。[code]@Override
protected String[] selectImports(AdviceMode adviceMode) {
switch (adviceMode) {
case PROXY:
return new String[] {AutoProxyRegistrar.class.getName(), ProxyTransactionManagementConfiguration.class.getName()};
case ASPECTJ:
return new String[] {TransactionManagementConfigUtils.TRANSACTION_ASPECT_CONFIGURATION_CLASS_NAME};
default:
return null;
}
}
[/code]
这里我们以ProxyTransactionManagementConfiguration为例:[code]@Configuration
public class ProxyTransactionManagementConfiguration extends AbstractTransactionManagementConfiguration {

@Bean(name = TransactionManagementConfigUtils.TRANSACTION_ADVISOR_BEAN_NAME)
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
public BeanFactoryTransactionAttributeSourceAdvisor transactionAdvisor() {
BeanFactoryTransactionAttributeSourceAdvisor advisor = new BeanFactoryTransactionAttributeSourceAdvisor();
advisor.setTransactionAttributeSource(transactionAttributeSource());
advisor.setAdvice(transactionInterceptor());
advisor.setOrder(this.enableTx.<Integer>getNumber("order"));
return advisor;
}

@Bean
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
public TransactionAttributeSource transactionAttributeSource() {
return new AnnotationTransactionAttributeSource();
}

@Bean
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
public TransactionInterceptor transactionInterceptor() {
TransactionInterceptor interceptor = new TransactionInterceptor();
interceptor.setTransactionAttributeSource(transactionAttributeSource());
if (this.txManager != null) {
interceptor.setTransactionManager(this.txManager);
}
return interceptor;
}

}
[/code]
在ProxyTransactionManagementConfiguration中的三个配置分别定义:
transactionAttributeSource()方法在Spring容器中产生一个AnnotationTransactionAttributeSource实例,它能将我们常用的@Transactional注解转换成在Spring框架中使用的TransactionAttribute,如果我们需要在@Transactional注解上hack一些功能,也可以在这里重新实现TransactionAttribute类。transactionAdvisor()方法根据transactionAttributeSource()定义了AOP的切点,也就是被@Transactional注解使用的方法。transactionInterceptor()则定义了拦截器TransactionInterceptor,被之前定义的切点(也就是transactionAttributeSource()方法中定义的切面)拦截到的方法都会被送到这个类的实例中执行。
到这里为止,事务拦截的部分已经完成了。再回到文章开头提到的另外一个配置:配置DataSourceTransactionManager事务管理器。

PlatformTransactionManager是Spring事务的核心接口,这个接口只包含了以下三个方法:[code]TransactionStatus getTransaction(TransactionDefinition definition) throws TransactionException;

void commit(TransactionStatus status) throws TransactionException;

void rollback(TransactionStatus status) throws TransactionException;
[/code]
DataSourceTransactionManager是PlatformTransactionManager的最基本实现,应用(Application)操作事务的动作都会在这个类中进行。从配置中也不难发现,如果用户自己配置了自定义的事务管理器(比如JPA,JTA等等),Spring会忽略这个这个transactionManager()配置方法:

@ConditionalOnMissingBean(PlatformTransactionManager.class)

当了解Spring配置事务的过程之后,方法调用时事务的执行过程也就变得清晰起来了。
所有需要使用事务的方法(即标记了@Transactional的方法),都会在TransactionInterceptor的invoke()调用,然后在在TransactionAspectSupport类的invokeWithinTransaction()方法中被执行。

在invokeWithinTransaction()方法中事务处理分成两种:
标准事务处理异步事务处理
这里我们就分析下标准事务的部分,其核心代码(TransactionAspectSupport#invokeWithinTransaction前半段)如下:[code]// If the transaction attribute is null, the method is non-transactional.
final TransactionAttribute txAttr = getTransactionAttributeSource().getTransactionAttribute(method, targetClass);
final PlatformTransactionManager tm = determineTransactionManager(txAttr);
final String joinpointIdentification = methodIdentification(method, targetClass, txAttr);

if (txAttr == null || !(tm instanceof CallbackPreferringPlatformTransactionManager)) {
// Standard transaction demarcation with getTransaction and commit/rollback calls.
// ①判断是否创建事务
TransactionInfo txInfo = createTransactionIfNecessary(tm, txAttr, joinpointIdentification);
Object retVal = null;
try {
// This is an around advice: Invoke the next interceptor in the chain.
// This will normally result in a target object being invoked.
retVal = invocation.proceedWithInvocation();
}
catch (Throwable ex) {
// target invocation exception
completeTransactionAfterThrowing(txInfo, ex);
throw ex;
}
finally {
cleanupTransactionInfo(txInfo);
}
// ②判断提交事务
commitTransactionAfterReturning(txInfo);
eturn retVal;
}
[/code]
这个部分中最重要的两个方法就是标记的两处,他们会分别去判断是否创建事务和提交事务。而invocation.proceedWithInvocation()则会执行应用中常规的方法,如果使用了JDBC的方法,SQL都会根据数据库的事务来处理。[code]/**
* 判断是否要创建新的事务
*/
protected TransactionInfo createTransactionIfNecessary(
PlatformTransactionManager tm, TransactionAttribute txAttr, final String joinpointIdentification) {

// If no name specified, apply method identification as transaction name.
if (txAttr != null && txAttr.getName() == null) {
txAttr = new DelegatingTransactionAttribute(txAttr) {
@Override
public String getName() {
return joinpointIdentification;
}
};
}

TransactionStatus status = null;
if (txAttr != null) {
if (tm != null) {
// 通过事务的属性,去上下文获取事务。如果不存在则会创建新的事务
// 详细代码见 org.springframework.transaction.support.AbstractPlatformTransactionManager#getTransaction
// 并在这个方法中开启事务
status = tm.getTransaction(txAttr);
} else {
if (logger.isDebugEnabled()) {
logger.debug("Skipping transactional joinpoint [" + joinpointIdentification +
"] because no transaction manager has been configured");
}
}
}
return prepareTransactionInfo(tm, txAttr, joinpointIdentification, status);
}
[/code]
在AbstractPlatformTransactionManager#getTransaction中会根据当前上下文的状态调用抽象方法doBegin()方法,也就是开启事务。
而在invokeWithinTransaction()方法标记的②处,会判断本次方法执行完是否要提交事务,这个方法中的核心是AbstractPlatformTransactionManager类的processCommit()方法:[code]private void processCommit(DefaultTransactionStatus status) throws TransactionException {
try {
boolean beforeCompletionInvoked = false;
try {
prepareForCommit(status);
triggerBeforeCommit(status);
triggerBeforeCompletion(status);
beforeCompletionInvoked = true;
boolean globalRollbackOnly = false;
if (status.isNewTransaction() || isFailEarlyOnGlobalRollbackOnly()) {
globalRollbackOnly = status.isGlobalRollbackOnly();
}
if (status.hasSavepoint()) {
if (status.isDebug()) {
logger.debug("Releasing transaction savepoint");
}
status.releaseHeldSavepoint();
}
else if (status.isNewTransaction()) {
if (status.isDebug()) {
logger.debug("Initiating transaction commit");
}
// ③
doCommit(status);
}
// Throw UnexpectedRollbackException if we have a global rollback-only
// marker but still didn't get a corresponding exception from commit.
if (globalRollbackOnly) {
throw new UnexpectedRollbackException(
"Transaction silently rolled back because it has been marked as rollback-only");
}
}
catch (UnexpectedRollbackException ex) {
// can only be caused by doCommit
triggerAfterCompletion(status, TransactionSynchronization.STATUS_ROLLED_BACK);
throw ex;
}
catch (TransactionException ex) {
// can only be caused by doCommit
if (isRollbackOnCommitFailure()) {
doRollbackOnCommitException(status, ex);
}
else {
triggerAfterCompletion(status, TransactionSynchronization.STATUS_UNKNOWN);
}
throw ex;
}
catch (RuntimeException ex) {
if (!beforeCompletionInvoked) {
triggerBeforeCompletion(status);
}
doRollbackOnCommitException(status, ex);
throw ex;
}
catch (Error err) {
if (!beforeCompletionInvoked) {
triggerBeforeCompletion(status);
}
doRollbackOnCommitException(status, err);
throw err;
}
// Trigger afterCommit callbacks, with an exception thrown there
// propagated to callers but the transaction still considered as committed.
try {
triggerAfterCommit(status);
}
finally {
triggerAfterCompletion(status, TransactionSynchronization.STATUS_COMMITTED);
}
}
finally {
cleanupAfterCompletion(status);
}
}
[/code]
以上的整个流程中,由@Transaction注解转换的TransactionAttribute类以及由TransactionAttribute产生的TransactionStatus管理着Spring事务的状态和流程。当完成当前上下文的事务之后Spring就会根据事务的状态,判断是否要提交当前事务,如果事务完成还需要释放当前事务占用的相关资源。

  查看全部
原文地址: http://www.jianshu.com/p/b81aaf6056f4
 
 
根据之前一篇文章的例子,我发现
Spring Boot
是自动完成事务的配置的,所以周末我特意翻了一段源码,探究了一把
Spring Boot
是如何完成这个自动配置的过程的。
首先在
Spring Boot
autoconfigure.jar
依赖中的
org.springframework.boot.autoconfigure.jdbc
包下有一个
DataSourceTransactionManagerAutoConfiguration
类。Spring启动的时候就会根据是否添加了JDBC相关的包来判断是否要加载这个配置类中的配置。
[code]@Configuration
@ConditionalOnClass({ JdbcTemplate.class, PlatformTransactionManager.class })
@AutoConfigureOrder(Ordered.LOWEST_PRECEDENCE)
@EnableConfigurationProperties(DataSourceProperties.class)
public class DataSourceTransactionManagerAutoConfiguration {

@Configuration
@ConditionalOnSingleCandidate(DataSource.class)
static class DataSourceTransactionManagerConfiguration {

private final DataSource dataSource;

private final TransactionManagerCustomizers transactionManagerCustomizers;

DataSourceTransactionManagerConfiguration(DataSource dataSource,
ObjectProvider<TransactionManagerCustomizers> transactionManagerCustomizers) {
this.dataSource = dataSource;
this.transactionManagerCustomizers = transactionManagerCustomizers
.getIfAvailable();
}

@Bean
@ConditionalOnMissingBean(PlatformTransactionManager.class)
public DataSourceTransactionManager transactionManager(
DataSourceProperties properties) {
DataSourceTransactionManager transactionManager = new DataSourceTransactionManager(
this.dataSource);
if (this.transactionManagerCustomizers != null) {
this.transactionManagerCustomizers.customize(transactionManager);
}
return transactionManager;
}

}

@ConditionalOnMissingBean(AbstractTransactionManagementConfiguration.class)
@Configuration
@EnableTransactionManagement(proxyTargetClass = true)
protected static class TransactionManagementConfiguration {

}

}
[/code]
以上这个配置类(
DataSourceTransactionManagerAutoConfiguration
)中有做了这样两个动作:
  1. 添加
    DataSourceTransactionManager
    事务管理器实例
  2. 触发
    @EnableTransactionManagement
    注解让当前项目启用事务。

这里先从第二个动作也就是触发
@EnableTransactionManagement
注解这个动作开始分析,看看启动的阶段
Spring
到底做了些什么。
[code]@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import(TransactionManagementConfigurationSelector.class)
public @interface EnableTransactionManagement {

boolean proxyTargetClass() default false;

AdviceMode mode() default AdviceMode.PROXY;

int order() default Ordered.LOWEST_PRECEDENCE;

}
[/code]
从中可以看出导入了另外一个实际的配置类
TransactionManagementConfigurationSelector
,这个类中仅有一个方法
selectImports()
,它会根据注解上的配置来选择
Spring AOP
中的通知配置,默认情况下是选择
PROXY
[code]@Override
protected String[] selectImports(AdviceMode adviceMode) {
switch (adviceMode) {
case PROXY:
return new String[] {AutoProxyRegistrar.class.getName(), ProxyTransactionManagementConfiguration.class.getName()};
case ASPECTJ:
return new String[] {TransactionManagementConfigUtils.TRANSACTION_ASPECT_CONFIGURATION_CLASS_NAME};
default:
return null;
}
}
[/code]
这里我们以
ProxyTransactionManagementConfiguration
为例:
[code]@Configuration
public class ProxyTransactionManagementConfiguration extends AbstractTransactionManagementConfiguration {

@Bean(name = TransactionManagementConfigUtils.TRANSACTION_ADVISOR_BEAN_NAME)
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
public BeanFactoryTransactionAttributeSourceAdvisor transactionAdvisor() {
BeanFactoryTransactionAttributeSourceAdvisor advisor = new BeanFactoryTransactionAttributeSourceAdvisor();
advisor.setTransactionAttributeSource(transactionAttributeSource());
advisor.setAdvice(transactionInterceptor());
advisor.setOrder(this.enableTx.<Integer>getNumber("order"));
return advisor;
}

@Bean
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
public TransactionAttributeSource transactionAttributeSource() {
return new AnnotationTransactionAttributeSource();
}

@Bean
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
public TransactionInterceptor transactionInterceptor() {
TransactionInterceptor interceptor = new TransactionInterceptor();
interceptor.setTransactionAttributeSource(transactionAttributeSource());
if (this.txManager != null) {
interceptor.setTransactionManager(this.txManager);
}
return interceptor;
}

}
[/code]
ProxyTransactionManagementConfiguration
中的三个配置分别定义:
  • transactionAttributeSource()
    方法在
    Spring
    容器中产生一个
    AnnotationTransactionAttributeSource
    实例,它能将我们常用的
    @Transactional
    注解转换成在
    Spring
    框架中使用的
    TransactionAttribute
    ,如果我们需要在
    @Transactional
    注解上hack一些功能,也可以在这里重新实现
    TransactionAttribute
    类。
  • transactionAdvisor()
    方法根据
    transactionAttributeSource()
    定义了AOP的切点,也就是被
    @Transactional
    注解使用的方法。
  • transactionInterceptor()
    则定义了拦截器
    TransactionInterceptor
    ,被之前定义的切点(也就是transactionAttributeSource()方法中定义的切面)拦截到的方法都会被送到这个类的实例中执行

到这里为止,事务拦截的部分已经完成了。再回到文章开头提到的另外一个配置:配置
DataSourceTransactionManager
事务管理器。

PlatformTransactionManager
Spring
事务的核心接口,这个接口只包含了以下三个方法:
[code]TransactionStatus getTransaction(TransactionDefinition definition) throws TransactionException;

void commit(TransactionStatus status) throws TransactionException;

void rollback(TransactionStatus status) throws TransactionException;
[/code]
DataSourceTransactionManager
PlatformTransactionManager
的最基本实现,应用(Application)操作事务的动作都会在这个类中进行。从配置中也不难发现,如果用户自己配置了自定义的事务管理器(比如JPA,JTA等等),
Spring
会忽略这个这个
transactionManager()
配置方法:


@ConditionalOnMissingBean(PlatformTransactionManager.class)


当了解
Spring
配置事务的过程之后,方法调用时事务的执行过程也就变得清晰起来了。
所有需要使用事务的方法(即标记了
@Transactional
的方法),都会在
TransactionInterceptor
invoke()
调用,然后在在
TransactionAspectSupport
类的
invokeWithinTransaction()
方法中被执行。

invokeWithinTransaction()
方法中事务处理分成两种:
  • 标准事务处理
  • 异步事务处理

这里我们就分析下标准事务的部分,其核心代码(
TransactionAspectSupport#invokeWithinTransaction
前半段)如下:
[code]// If the transaction attribute is null, the method is non-transactional.
final TransactionAttribute txAttr = getTransactionAttributeSource().getTransactionAttribute(method, targetClass);
final PlatformTransactionManager tm = determineTransactionManager(txAttr);
final String joinpointIdentification = methodIdentification(method, targetClass, txAttr);

if (txAttr == null || !(tm instanceof CallbackPreferringPlatformTransactionManager)) {
// Standard transaction demarcation with getTransaction and commit/rollback calls.
// ①判断是否创建事务
TransactionInfo txInfo = createTransactionIfNecessary(tm, txAttr, joinpointIdentification);
Object retVal = null;
try {
// This is an around advice: Invoke the next interceptor in the chain.
// This will normally result in a target object being invoked.
retVal = invocation.proceedWithInvocation();
}
catch (Throwable ex) {
// target invocation exception
completeTransactionAfterThrowing(txInfo, ex);
throw ex;
}
finally {
cleanupTransactionInfo(txInfo);
}
// ②判断提交事务
commitTransactionAfterReturning(txInfo);
eturn retVal;
}
[/code]
这个部分中最重要的两个方法就是标记的两处,他们会分别去判断是否创建事务和提交事务。而
invocation.proceedWithInvocation()
则会执行应用中常规的方法,如果使用了JDBC的方法,SQL都会根据数据库的事务来处理。
[code]/**
* 判断是否要创建新的事务
*/
protected TransactionInfo createTransactionIfNecessary(
PlatformTransactionManager tm, TransactionAttribute txAttr, final String joinpointIdentification) {

// If no name specified, apply method identification as transaction name.
if (txAttr != null && txAttr.getName() == null) {
txAttr = new DelegatingTransactionAttribute(txAttr) {
@Override
public String getName() {
return joinpointIdentification;
}
};
}

TransactionStatus status = null;
if (txAttr != null) {
if (tm != null) {
// 通过事务的属性,去上下文获取事务。如果不存在则会创建新的事务
// 详细代码见 org.springframework.transaction.support.AbstractPlatformTransactionManager#getTransaction
// 并在这个方法中开启事务
status = tm.getTransaction(txAttr);
} else {
if (logger.isDebugEnabled()) {
logger.debug("Skipping transactional joinpoint [" + joinpointIdentification +
"] because no transaction manager has been configured");
}
}
}
return prepareTransactionInfo(tm, txAttr, joinpointIdentification, status);
}
[/code]
AbstractPlatformTransactionManager#getTransaction
中会根据当前上下文的状态调用抽象方法
doBegin()
方法,也就是开启事务。
而在
invokeWithinTransaction()
方法标记的②处,会判断本次方法执行完是否要提交事务,这个方法中的核心是
AbstractPlatformTransactionManager
类的
processCommit()
方法:
[code]private void processCommit(DefaultTransactionStatus status) throws TransactionException {
try {
boolean beforeCompletionInvoked = false;
try {
prepareForCommit(status);
triggerBeforeCommit(status);
triggerBeforeCompletion(status);
beforeCompletionInvoked = true;
boolean globalRollbackOnly = false;
if (status.isNewTransaction() || isFailEarlyOnGlobalRollbackOnly()) {
globalRollbackOnly = status.isGlobalRollbackOnly();
}
if (status.hasSavepoint()) {
if (status.isDebug()) {
logger.debug("Releasing transaction savepoint");
}
status.releaseHeldSavepoint();
}
else if (status.isNewTransaction()) {
if (status.isDebug()) {
logger.debug("Initiating transaction commit");
}
// ③
doCommit(status);
}
// Throw UnexpectedRollbackException if we have a global rollback-only
// marker but still didn't get a corresponding exception from commit.
if (globalRollbackOnly) {
throw new UnexpectedRollbackException(
"Transaction silently rolled back because it has been marked as rollback-only");
}
}
catch (UnexpectedRollbackException ex) {
// can only be caused by doCommit
triggerAfterCompletion(status, TransactionSynchronization.STATUS_ROLLED_BACK);
throw ex;
}
catch (TransactionException ex) {
// can only be caused by doCommit
if (isRollbackOnCommitFailure()) {
doRollbackOnCommitException(status, ex);
}
else {
triggerAfterCompletion(status, TransactionSynchronization.STATUS_UNKNOWN);
}
throw ex;
}
catch (RuntimeException ex) {
if (!beforeCompletionInvoked) {
triggerBeforeCompletion(status);
}
doRollbackOnCommitException(status, ex);
throw ex;
}
catch (Error err) {
if (!beforeCompletionInvoked) {
triggerBeforeCompletion(status);
}
doRollbackOnCommitException(status, err);
throw err;
}
// Trigger afterCommit callbacks, with an exception thrown there
// propagated to callers but the transaction still considered as committed.
try {
triggerAfterCommit(status);
}
finally {
triggerAfterCompletion(status, TransactionSynchronization.STATUS_COMMITTED);
}
}
finally {
cleanupAfterCompletion(status);
}
}
[/code]
以上的整个流程中,由
@Transaction
注解转换的
TransactionAttribute
类以及由
TransactionAttribute
产生的
TransactionStatus
管理着
Spring
事务的状态和流程。当完成当前上下文的事务之后
Spring
就会根据事务的状态,判断是否要提交当前事务,如果事务完成还需要释放当前事务占用的相关资源。

 

深入浅出 spring-data-elasticsearch 系列 - 概述及入门(二)

泥瓦匠BYSocket 发表了文章 • 0 个评论 • 1316 次浏览 • 2017-06-08 15:45 • 来自相关话题

本文目录
一、spring-data-elasticsearch 是什么?
1.1 Spring Data
1.2 Spring Data Elasticsearch
二、spring-data-elasticsearch 快速入门
2.1 pom.xml 依赖
2.2 ElasticsearchRepository
2.3 ElasticsearchTemplate
2.4 使用案例
三、spring-data-elasticsearch 和 elasticsearch 版本
四、小结
 

一、spring-data-elasticsearch 是什么?
1.1 Spring Data
要了解 spring-data-elasticsearch 是什么,首先了解什么是 Spring Data。
Spring Data 基于 Spring 为数据访问提供一种相似且一致性的编程模型,并保存底层数据存储的。

1.2 Spring Data Elasticsearch
spring-data-elasticsearch 是 Spring Data 的 Community modules 之一,是 Spring Data 对 Elasticsearch 引擎的实现。
Elasticsearch 默认提供轻量级的 HTTP Restful 接口形式的访问。相对来说,使用 HTTP Client 调用也很简单。但 spring-data-elasticsearch 可以更快的支持构建在 Spring 应用上,比如在 application.properties 配置 ES 节点信息和 spring-boot-starter-data-elasticsearch 依赖,直接在 Spring Boot 应用上使用。

二、spring-data-elasticsearch 快速入门
2.1 pom.xml 依赖<dependency>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-elasticsearch</artifactId>
<version>x.y.z.RELEASE</version>
</dependency>
2.2 ElasticsearchRepository
ES 通用的存储接口的一种默认实现。Spring 根据接口定义的方法名,具体执行对应的数据存储实现。
ElasticsearchRepository 继承 ElasticsearchCrudRepository ,ElasticsearchCrudRepository 继承 PagingAndSortingRepository。所以一般 CRUD 带分页已经支持。如图:
 






2.3 ElasticsearchTemplate
ES 数据操作的中心支持类。和 JdbcTemplate 一样,几乎所有操作都可以使用 ElasticsearchTemplate 来完成。
ElasticsearchTemplate 实现了 ElasticsearchOperations 和 ApplicationContextAware 接口。ElasticsearchOperations 接口提供了 ES 相关的操作,并将 ElasticsearchTemplate 加入到 Spring 上下文。如图:
 





 
2.4 使用案例
拿官方案例来吧,详细介绍了 Book ES 对象的接口实现。
可以看出,book 拥有 name 和 price 两个属性。下面支持  name 和 price 列表 ES 查询,分页查询,范围查询等。还有可以利用注解实现 DSL 操作。 public interface BookRepository extends Repository<Book, String> {
List<Book> findByNameAndPrice(String name, Integer price);
List<Book> findByNameOrPrice(String name, Integer price);
Page<Book> findByName(String name,Pageable page);
Page<Book> findByNameNot(String name,Pageable page);
Page<Book> findByPriceBetween(int price,Pageable page);
Page<Book> findByNameLike(String name,Pageable page);
@Query("{\"bool\" : {\"must\" : {\"term\" : {\"message\" : \"?0\"}}}}")
Page<Book> findByMessage(String message, Pageable pageable);
}

三、spring-data-elasticsearch 和 elasticsearch 版本
SpringBoot 1.5+ 目前仅支持 ElasticSearch 2.3.2,所以如果想要使用最新的 ES。可以通过默认的轻量级的 HTTP 去调用实现。其版本对应如下:

spring data elasticsearch    elasticsearch
3.0.0.BUILD-SNAPSHOT    5.4.0
2.0.4.RELEASE                    2.4.0
2.0.0.RELEASE                    2.2.0
1.4.0.M1                               1.7.3
1.3.0.RELEASE                    1.5.2
1.2.0.RELEASE                    1.4.4
1.1.0.RELEASE                    1.3.2
1.0.0.RELEASE                    1.1.1

四、小结
本小结介绍了 spring-data-elasticsearch 是概述以及它的入门,还有 spring-data-elasticsearch 核心接口及版本的情况。

资料:
项目地址
https://github.com/spring-proj ... earch
官方文档
http://docs.spring.io/spring-data/elasticsearch/docs/current/reference/html/ 

本文作者: 泥瓦匠
原文链接: http://www.bysocket.com
版权归作者所有,转载请注明出处 查看全部
本文目录
一、spring-data-elasticsearch 是什么?
1.1 Spring Data
1.2 Spring Data Elasticsearch
二、spring-data-elasticsearch 快速入门
2.1 pom.xml 依赖
2.2 ElasticsearchRepository
2.3 ElasticsearchTemplate
2.4 使用案例
三、spring-data-elasticsearch 和 elasticsearch 版本
四、小结
 

一、spring-data-elasticsearch 是什么?
1.1 Spring Data
要了解 spring-data-elasticsearch 是什么,首先了解什么是 Spring Data。
Spring Data 基于 Spring 为数据访问提供一种相似且一致性的编程模型,并保存底层数据存储的。

1.2 Spring Data Elasticsearch
spring-data-elasticsearch 是 Spring Data 的 Community modules 之一,是 Spring Data 对 Elasticsearch 引擎的实现。
Elasticsearch 默认提供轻量级的 HTTP Restful 接口形式的访问。相对来说,使用 HTTP Client 调用也很简单。但 spring-data-elasticsearch 可以更快的支持构建在 Spring 应用上,比如在 application.properties 配置 ES 节点信息和 spring-boot-starter-data-elasticsearch 依赖,直接在 Spring Boot 应用上使用。

二、spring-data-elasticsearch 快速入门
2.1 pom.xml 依赖
<dependency>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-elasticsearch</artifactId>
<version>x.y.z.RELEASE</version>
</dependency>

2.2 ElasticsearchRepository
ES 通用的存储接口的一种默认实现。Spring 根据接口定义的方法名,具体执行对应的数据存储实现。
ElasticsearchRepository 继承 ElasticsearchCrudRepository ,ElasticsearchCrudRepository 继承 PagingAndSortingRepository。所以一般 CRUD 带分页已经支持。如图:
 
esr.png



2.3 ElasticsearchTemplate
ES 数据操作的中心支持类。和 JdbcTemplate 一样,几乎所有操作都可以使用 ElasticsearchTemplate 来完成。
ElasticsearchTemplate 实现了 ElasticsearchOperations 和 ApplicationContextAware 接口。ElasticsearchOperations 接口提供了 ES 相关的操作,并将 ElasticsearchTemplate 加入到 Spring 上下文。如图:
 
est.png


 
2.4 使用案例
拿官方案例来吧,详细介绍了 Book ES 对象的接口实现。
可以看出,book 拥有 name 和 price 两个属性。下面支持  name 和 price 列表 ES 查询,分页查询,范围查询等。还有可以利用注解实现 DSL 操作。
    public interface BookRepository extends Repository<Book, String> {
List<Book> findByNameAndPrice(String name, Integer price);
List<Book> findByNameOrPrice(String name, Integer price);
Page<Book> findByName(String name,Pageable page);
Page<Book> findByNameNot(String name,Pageable page);
Page<Book> findByPriceBetween(int price,Pageable page);
Page<Book> findByNameLike(String name,Pageable page);
@Query("{\"bool\" : {\"must\" : {\"term\" : {\"message\" : \"?0\"}}}}")
Page<Book> findByMessage(String message, Pageable pageable);
}


三、spring-data-elasticsearch 和 elasticsearch 版本
SpringBoot 1.5+ 目前仅支持 ElasticSearch 2.3.2,所以如果想要使用最新的 ES。可以通过默认的轻量级的 HTTP 去调用实现。其版本对应如下:

spring data elasticsearch    elasticsearch
3.0.0.BUILD-SNAPSHOT    5.4.0
2.0.4.RELEASE                    2.4.0
2.0.0.RELEASE                    2.2.0
1.4.0.M1                               1.7.3
1.3.0.RELEASE                    1.5.2
1.2.0.RELEASE                    1.4.4
1.1.0.RELEASE                    1.3.2
1.0.0.RELEASE                    1.1.1

四、小结
本小结介绍了 spring-data-elasticsearch 是概述以及它的入门,还有 spring-data-elasticsearch 核心接口及版本的情况。

资料:
项目地址
https://github.com/spring-proj ... earch
官方文档
http://docs.spring.io/spring-data/elasticsearch/docs/current/reference/html/ 


本文作者: 泥瓦匠
原文链接: http://www.bysocket.com
版权归作者所有,转载请注明出处


深入浅出 spring-data-elasticsearch 之 ElasticSearch 架构初探(一)

泥瓦匠BYSocket 发表了文章 • 2 个评论 • 2560 次浏览 • 2017-06-04 17:20 • 来自相关话题

本文目录
一、Elasticsearch 基本术语
1.1 文档(Document)、索引(Index)、类型(Type)文档三要素
1.2 集群(Cluster)、节点(Node)、分片(Shard)分布式三要素
二、Elasticsearch 工作原理
2.1 文档存储的路由
2.2 如何健康检查
2.3 如何水平扩容
三、小结

一、Elasticsearch 基本术语

1.1 文档(Document)、索引(Index)、类型(Type)文档三要素
文档(Document)
文档,在面向对象观念就是一个对象。在 ES 里面,是一个大 JSON 对象,是指定了唯一 ID 的最底层或者根对象。文档的位置由 _index、_type 和 _id 唯一标识。

索引(Index)
索引,用于区分文档成组,即分到一组的文档集合。索引,用于存储文档和使文档可被搜索。比如项目存索引 project 里面,交易存索引 sales 等。

类型(Type)
类型,用于区分索引中的文档,即在索引中对数据逻辑分区。比如索引 project 的项目数据,根据项目类型 ui 项目、插画项目等进行区分。

和关系型数据库 MySQL 做个类比:
Document 类似于 Record
Type 类似于 Table
Index 类似于 Database

1.2 集群(Cluster)、节点(Node)、分片(Shard)分布式三要素
集群(Cluster)
服务器集群大家都知道,这里 ES 也是类似的。多个 ElasticSearch 运行实例(节点)组合的组合体是 ElasticSearch 集群。
ElasticSearch 是天然的分布式,通过水平扩容为集群添加更多节点。
集群是去中心化的,有一个主节点(Master)。主节点是动态选举,因此不会出现单点故障。

那分片和节点的配置呢?
节点(Node)
一个 ElasticSearch 运行实例就是节点。顺着集群来,任何节点都可以被选举成为主节点。主节点负责集群内所以变更,比如索引的增加、删除等。所以集群不会因为主节点流量的增大成为瓶颈。因为任何节点都会成为主节点。
下面有 3 个节点,第 1 个节点有:2 个主分片和 1 个副分片。如图:





那么,只有一个节点的 ElasticSearch 服务会存在瓶颈。如图:





分片(Shard)
分片,是 ES 节点中最小的工作单元。分片仅仅保存全部数据的一部分,分片的集合是 ES 的索引。分片包括主分片和副分片,主分片是副分片的拷贝。主分片和副分片地工作基本没有大的区别。
在索引中全文搜索,然后会查询到每个分片,将每个分配的结果进行全局地收集处理,并返回。

二、Elasticsearch 工作原理

2.1 文档存储的路由
当索引到一个文档(如:报价系统),具体的文档数据(如:报价数据)会存储到一个分片。具体文档数据会被切分,并分别存储在分片 1 或者 分片 2 … 
那么如何确定存在哪个分片呢?
存储路由过程由下面地公式决定:shard = hash(routing) % number_of_primary_shards
routing 是可变值,支持自定义,默认文档 _id。
hash 函数生成数字,经过取余算法得到余数,那么这个余数就是分片的位置。
这是不是有点负载均衡的类似。
 
2.2 如何健康检查
集群名,集群的健康状态GET http://127.0.0.1:9200/_cluster/stats
{
"cluster_name": "elasticsearch",
"status": "green",
"timed_out": false,
"number_of_nodes": 1,
"number_of_data_nodes": 1,
"active_primary_shards": 0,
"active_shards": 0,
"relocating_shards": 0,
"initializing_shards": 0,
"unassigned_shards": 0
}
status 字段是需要我们关心的。状态可能是下列三个值之一:green
所有的主分片和副本分片都已分配。你的集群是 100% 可用的。
yellow
所有的主分片已经分片了,但至少还有一个副本是缺失的。不会有数据丢失,所以搜索结果依然是完整的。高可用会弱化把 yellow 想象成一个需要及时调查的警告。
red
至少一个主分片(以及它的全部副本)都在缺失中。这意味着你在缺少数据:搜索只能返回部分数据,而分配到这个分片上的写入请求会返回一个异常。


active_primary_shards 集群中的主分片数量
active_shards 所有分片的汇总值
relocating_shards 显示当前正在从一个节点迁往其他节点的分片的数量。通常来说应该是 0,不过在 Elasticsearch 发现集群不太均衡时,该值会上涨。比如说:添加了一个新节点,或者下线了一个节点。
initializing_shards 刚刚创建的分片的个数。
unassigned_shards 已经在集群状态中存在的分片。
 
2.3 如何水平扩容
主分片在索引创建已经确定。读操作可以同时被主分片和副分片处理。因此,更多的分片,会拥有更高的吞吐量。自然,需要增加更多的硬件资源支持吞吐量。
说明,这里无法提高性能,因为每个分片获得的资源会变少。
动态调整副本分片数,按需伸缩集群,比如把副本数默认值为 1 增加到 2:PUT /blogs/_settings
{
"number_of_replicas" : 2
}

三、小结
简单初探了下 ElasticSearch 的相关内容。后面会主要落地到实战,关于  spring-data-elasticsearch 这块的实战。

最后,《 深入浅出 spring-data-elasticsearch 》小连载目录如下:
深入浅出 spring-data-elasticsearch - ElasticSearch 架构初探(一)
深入浅出 spring-data-elasticsearch - 概述(二)
深入浅出 spring-data-elasticsearch - 基本案例详解(三)
深入浅出 spring-data-elasticsearch - 复杂案例详解(四)
深入浅出 spring-data-elasticsearch - 架构原理以及源码浅析(五)
 

资料:
官方《Elasticsearch: 权威指南》
https://www.elastic.co/guide/c ... .html
 

本文作者: 泥瓦匠
原文链接: http://www.bysocket.com
版权归作者所有,转载请注明出处 查看全部
本文目录
一、Elasticsearch 基本术语
1.1 文档(Document)、索引(Index)、类型(Type)文档三要素
1.2 集群(Cluster)、节点(Node)、分片(Shard)分布式三要素
二、Elasticsearch 工作原理
2.1 文档存储的路由
2.2 如何健康检查
2.3 如何水平扩容
三、小结

一、Elasticsearch 基本术语

1.1 文档(Document)、索引(Index)、类型(Type)文档三要素
文档(Document)
文档,在面向对象观念就是一个对象。在 ES 里面,是一个大 JSON 对象,是指定了唯一 ID 的最底层或者根对象。文档的位置由 _index、_type 和 _id 唯一标识。

索引(Index)
索引,用于区分文档成组,即分到一组的文档集合。索引,用于存储文档和使文档可被搜索。比如项目存索引 project 里面,交易存索引 sales 等。

类型(Type)
类型,用于区分索引中的文档,即在索引中对数据逻辑分区。比如索引 project 的项目数据,根据项目类型 ui 项目、插画项目等进行区分。

和关系型数据库 MySQL 做个类比
Document 类似于 Record
Type 类似于 Table
Index 类似于 Database

1.2 集群(Cluster)、节点(Node)、分片(Shard)分布式三要素
集群(Cluster)
服务器集群大家都知道,这里 ES 也是类似的。多个 ElasticSearch 运行实例(节点)组合的组合体是 ElasticSearch 集群。
ElasticSearch 是天然的分布式,通过水平扩容为集群添加更多节点。
集群是去中心化的,有一个主节点(Master)。主节点是动态选举,因此不会出现单点故障。

那分片和节点的配置呢?
节点(Node
一个 ElasticSearch 运行实例就是节点。顺着集群来,任何节点都可以被选举成为主节点。主节点负责集群内所以变更,比如索引的增加、删除等。所以集群不会因为主节点流量的增大成为瓶颈。因为任何节点都会成为主节点。
下面有 3 个节点,第 1 个节点有:2 个主分片和 1 个副分片。如图:
elas_node.png


那么,只有一个节点的 ElasticSearch 服务会存在瓶颈。如图:
elas_0201.png


分片(Shard)
分片,是 ES 节点中最小的工作单元。分片仅仅保存全部数据的一部分,分片的集合是 ES 的索引。分片包括主分片和副分片,主分片是副分片的拷贝。主分片和副分片地工作基本没有大的区别。
在索引中全文搜索,然后会查询到每个分片,将每个分配的结果进行全局地收集处理,并返回。

二、Elasticsearch 工作原理

2.1 文档存储的路由
当索引到一个文档(如:报价系统),具体的文档数据(如:报价数据)会存储到一个分片。具体文档数据会被切分,并分别存储在分片 1 或者 分片 2 … 
那么如何确定存在哪个分片呢?
存储路由过程由下面地公式决定:
shard = hash(routing) % number_of_primary_shards

routing 是可变值,支持自定义,默认文档 _id。
hash 函数生成数字,经过取余算法得到余数,那么这个余数就是分片的位置。
这是不是有点负载均衡的类似。
 
2.2 如何健康检查
集群名,集群的健康状态
GET http://127.0.0.1:9200/_cluster/stats 
{
"cluster_name": "elasticsearch",
"status": "green",
"timed_out": false,
"number_of_nodes": 1,
"number_of_data_nodes": 1,
"active_primary_shards": 0,
"active_shards": 0,
"relocating_shards": 0,
"initializing_shards": 0,
"unassigned_shards": 0
}

status 字段是需要我们关心的。状态可能是下列三个值之一:
green
所有的主分片和副本分片都已分配。你的集群是 100% 可用的。
yellow
所有的主分片已经分片了,但至少还有一个副本是缺失的。不会有数据丢失,所以搜索结果依然是完整的。高可用会弱化把 yellow 想象成一个需要及时调查的警告。
red
至少一个主分片(以及它的全部副本)都在缺失中。这意味着你在缺少数据:搜索只能返回部分数据,而分配到这个分片上的写入请求会返回一个异常。


active_primary_shards 集群中的主分片数量
active_shards 所有分片的汇总值
relocating_shards 显示当前正在从一个节点迁往其他节点的分片的数量。通常来说应该是 0,不过在 Elasticsearch 发现集群不太均衡时,该值会上涨。比如说:添加了一个新节点,或者下线了一个节点。
initializing_shards 刚刚创建的分片的个数。
unassigned_shards 已经在集群状态中存在的分片。
 
2.3 如何水平扩容
主分片在索引创建已经确定。读操作可以同时被主分片和副分片处理。因此,更多的分片,会拥有更高的吞吐量。自然,需要增加更多的硬件资源支持吞吐量。
说明,这里无法提高性能,因为每个分片获得的资源会变少。
动态调整副本分片数,按需伸缩集群,比如把副本数默认值为 1 增加到 2:
PUT /blogs/_settings
{
"number_of_replicas" : 2
}


三、小结
简单初探了下 ElasticSearch 的相关内容。后面会主要落地到实战,关于  spring-data-elasticsearch 这块的实战。

最后,《 深入浅出 spring-data-elasticsearch 》小连载目录如下:
深入浅出 spring-data-elasticsearch - ElasticSearch 架构初探(一)
深入浅出 spring-data-elasticsearch - 概述(二)
深入浅出 spring-data-elasticsearch - 基本案例详解(三)
深入浅出 spring-data-elasticsearch - 复杂案例详解(四)
深入浅出 spring-data-elasticsearch - 架构原理以及源码浅析(五)
 

资料:
官方《Elasticsearch: 权威指南》
https://www.elastic.co/guide/c ... .html
 


本文作者: 泥瓦匠
原文链接: http://www.bysocket.com
版权归作者所有,转载请注明出处