ramostear.comramostear.com 谭朝红的技术分享博客

格言 编程是一门技术,也是一门艺术 !

Ehcache:Another CacheManager with same name exception

Ehcache:Another CacheManager with same name exception

Ehcache:Another CacheManager with same name异常

​ 在Spring Boot中开启热部署功能后,整合Shiro和Ehcache缓存时发生如下的异常信息:

Caused by: net.sf.ehcache.CacheException: Another CacheManager with same name 'shiro-cache' already exists in the same VM. Please provide unique names for each CacheManager in the config or do one of following:
1. Use one of the CacheManager.create() static factory methods to reuse same CacheManager with same name or create one if necessary
2. Shutdown the earlier cacheManager before creating new one with same name.

导致此错误的原因是针对新版本(2.5以后)的Ehcache,CacheManager使用了单例模式,当使用Spring Boot devtools开启热加载功能时,在加载SecurityManager Bean 对象实例时,会创建多个CacheManager实例,从而导致上述的异常。

​ 在此之前,网上已给出很多的解决方案,如降低Ehcache版本,设置缓存shared属性等。那么,如何在不改变当前框架版本的情况下修复上述异常呢?下面是笔者的完整解决方案。

1.改造前的代码

​ 下面的代码片段是改造前的示例:

在此示例中,只是简单的创建了ehcacheManager对象实例,并设置了缓存文件的存放地址。而新版本中,CacheManager要求是单例模式,因此需要使用EhCachManagerFactoryBean对象来限定cacheManager为共享模式。EhCachManagerFactoryBean是Spring提供的ehcache实现类。接下来我们看看如何改造。

2. 改造后的代码

​ 修改的地方一共有三处,它们是:配置EhcacheManagerFactoryBean并设置其为共享模式,配置DefaultAdvisorAutoProxyCreator,让Spring 来管理Shiro的Bean生命周期,配置lifecycleBeanPostProcessor,并让DefaultAdvisorAutoProxyCreator依赖于此对象。

2.1 添加EhcacheManagerFactoryBean配置项

​ 在原有的配置文件中添加EhcacheManagerFactoryBean配置项,并将shared属性设置为true,示例代码如下:

2.2 添加LifecycleBeanPostProcessor配置项

​ 添加LifecycleBeanPostProcessor配置项,LifecycleBeanPostProcessor将统一管理Initializable和Destroyable的实现类,从而达到统一管理Shiro Bean的生命周期的目的。示例代码如下:

2.3 添加DefaultAdvisorAutoProxyCreator配置项

DefaultAdvisorAutoProxyCreator的作用是动态代理Shiro的事务,最终将事务交由Spring进行统一管理。此配置项需要依赖于LifecycleBeanPostProcessor。示例代码如下:

通过添加上述的三个配置项,在Spring Boot中开启热加载功能就可以避免Another CacheManager with same name的异常出现。

正确甄别API & REST API & RESTful API & Web Service之间的差异与联系

正确甄别API & REST API & RESTful API & Web Service之间的差异与联系

​ 看到API你会想起什么?是接口、第三方调用、还是API文档?初看你可能会觉得这太熟悉了,这不是系统开发日常系列吗?但你仔细想一想,你会发现API的概念在你脑海里是如此的模糊。如何你通过搜索引擎检索API,你会看到类似这样的信息:API——Application Programming Interface(应用程序编程接口),这太抽象了。接下来,我将结合在开发中总结的一些经验,以通俗的方式聊聊API、REST API、RESTful API以及Web Service这四者之间的联系与区别。

1、API 与 REST API

​ 什么是API?这里引述维基百科给出的定义:应用程序接口(英语:Application Programming Interface,缩写:API;又称为应用编程接口)是软件系统不同组成部分衔接的约定。这个对API的定义太过于广泛和抽象,而通俗的讲,API是一段应用程序与另一段应用程序相互“交流”的方式(协议)。在Web应用程开发中,API是我们通过网络进行数据检索的一种主要方式,API文档将告知你检索数据的URL列表、查询参数、请求方式以及响应状态,其目的是降低Web应用程序开发难度,共享两个应用程序之间的数据(文本、音频、视频、图片等),而屏蔽其内部复杂的实现细节。

​ REST是Representational State Transfer的缩写,直译过来就是:表述状态的转移。REST API是一组关于如何构建Web应用程序API的架构规则、标准或指导,或者说REST API是遵循API原则的一种架构风格。REST是专门针对Web应用程序而设计的,其目的在于降低开发的复杂度,提高系统的可伸缩性。下面是设计REST风格的系统架构时需要满足或者遵循的一些基本条件和原则:

  • 1、在REST架构中,Web中所有的事物(文本、音频、视频、图片、链接)都可以被统一的抽象为资源(resource)
  • 2、在REST架构中,每一个资源都有与之对应的唯一资源标识符(resource identifier),当资源的状态发生改变时,资源标识符不会发生改变
  • 3、在REST架构中,所有的操作都是无状态的。REST架构遵循CRUD原则,所有的资源都可以通过GET、POST、PUT和DELETE这四种行为完成对应的操作。
  • 4、可缓存(可选项),在REST架构中需要缓存来有效的处理大批量的请求
  • 5、接口一致

​ 现在,了解了API和REST API的基本概念,那这两者之间有什么异同?如果按照数学上集合的概念来解释API与REST API之间的联系与区别,API是REST API的超集,REST API 是API的子集;所有的REST API都是API,但不是所有的API都是REST API。更通俗的解释是:所有的男人都是人,但不是所有的人都是男人。

2、REST API 与RESTful API

​ 在第一小节中,了解了什么是REST API,接下来聊聊REST API与RESTful API之间的异同。很多初学者很容易将这两者等同起来,认为RESTful API就是REST API,这可能是单纯的从字面上去理解了,当你深入的去了解两者的本质后,你会发现其实不然。REST API是Web API设计的一种规范或者指导原则,而RESTful API则是这中架构设计原则或者规范的一种具体实现方式。也就是说,RESTful API是REST API的非正式实现方式,因为实现REST API的方式有很多,RESTful API只是其中一种,且没有完全满足REST API的所有设计原则,每个开发者在实现REST 架构时的则重点都会有差别。

​ 很多初学者容易将REST API与RESTful API两者的概念搞混淆,我想可能只是看字面意思,而没有关注它们本身的含义(就像认识中文字一样,有边读边,无边读中间,断章取义了)。这就好比很多人会把变性人等同于女人,变性人可能五官的表象看起来和女人一样,但变性人不能生育,它只是满足了定义一个女性的大多数条件(实现),但本质上不是女人。

​ 接下来,通过一个简单的例子以加深对REST API和RESTful API的理解。下面将给出一个执行CURD操作的RESTful API设计案例:

说明:resource代指某种资源的名称,可以时student(学生)、teacher(老师)、book(书籍)等等,通常使用名词来表示;{id}则代指某种资源的唯一标识符(resource identifier)

下面是一个具体的小例子,以学生管理为例,设计学生管理的API。学生资源包括ID,姓名和所学课程信息,学生资源信息如下:

​ 现在,我们需要将学生数据保存到数据库,然后执行查询、修改和删除学生数据的操作。学生管理API的使用者调用的API如下:

​ 前面的内容说到,API共享数据资源,而屏蔽内部实现,API的使用者(客户端)关注的是资源(读懂数据),并不需要了解API内部构造;API的提供者(服务端)只关注自己的内部实现,而不关系API使用者(客户端)的状态。为了加深对这一概念的理解,下面给出学生管理API的内部实现示例:

说明:

示例代码是基于Spring MVC进行实现的。

除了上述的内容之外,你还可以通过提供键值对的方式对查询数据进行过滤,如获取所有的学生数据时,只想获取性别为女性的学生数据,则可以通过这样的方式来完成:

[GET] http://www.example.com/students?gender=female

Tip:如果API拥有对数据过滤的功能,对应服务端的API实现代码也需要做调整。

​ 在前面的内容中,我们提到RESTful API是REST API的非正式实现方式或规范。为什么这么说呢?因为在RESTful API的设计中,我们完全可以通过GET的方式完成CURD操作,也可以通过DELETE行为来创建资源,通过POST行为来修改资源,它的实现方式并不严谨或者说并没有严格按照REST API提出的约束条件来进行。所以说RESTful API是REST API的非正式实现方式

3、REST与Web Service

3-1、什么是Web Service?

​ 如万维网联盟(W3C)所述,Web Service提供了在各种平台和/或框架上运行的不同软件应用程序之间可以进行互操作的标准方法。Web Service的特点是具有良好的互操作性和可扩展性,以及由于使用XML而可以对程序处理过程进行描述。它们可以以松散耦合的方式组合不同的服务以实现复杂的操作。提供简单服务的程序可以通过相互交互,以提供复杂的增值服务。

​ 两个Web Service之间主要通过HTTP网络协议进行通信,如我们熟知的SOA(面向服务的体系架构),主要依赖于XML-RPC和SOAP(Simple Object Access Protocol,即简单对象访问协议)。

Tip:千万不要将SOA(面向服务体系架构)和SOAP(简单对象访问协议)搞混,前者是一种架构设计形式,后者是一种数据交换协议。

​ 简单的一个示例:假设一个Web Service A提供允许其他应用程序通过URL获取用户信息的功能:[GET] http://www.abc.com/{id}。id是用户的唯一标识符,请求此URL将获得用户信息。现在假设浏览器、手机、桌面应用程序的用户都要获取服务A提供的用户信息,这三者只需要请求服务A提供的URL地址,并输入用户id信息即可。至于者三个不同客户端的实现方式(编程语言)是什么与服务A 没有任何关系,只要能够解析出服务A返回的XML文档即可。这样,应用程序之间交换数据就可以不用依赖于具体的语言和环境。这就好比不同国家不同语言的人,只要能够知晓对方语言的语法结构,两个人就可以进行交流。

3-2、Web Service的优点

​ 使用Web Service有如下的几个优点:

  • 1、互操作性:Web Service允许应用程序之间进行通信,交换数据和共享服务。
  • 2、可用性:Web Service的功能可以从简单的信息查找到复杂的算法计算。
  • 3、可重用性:Web Service之间可以相互组合,以提供更为复杂的服务,由于其互操作性的特点,可以轻松的在其他的服务中重用Web Service组件,提高了服务的重用率。
  • 4、易于部署:Web Service可以部署在基于Internet标准的容器中,如Apache、Axis2等,以提供HTTP或者WSDL(网络服务定义语言)驱动的服务。
  • 5、成本低:Web Service是通过打包成Web服务组件进行部署,从而降低了使用的成本。

3-3、Web Service的类型

​ 目前,Web Service主要有两大流派:

  • 1、基于SOAP的Web Service : SOAP(简单对象访问协议)是一种基于XML的协议,用以访问Web Service。其接口以机器可处理的格式进行描述,称为WSDL(Web服务定义语言)文档。通过使用标准的的XML文档来描述Web Service,在XML文件中,会详细记录接口的信息,如消息的格式、传输协议以及交互的位置等信息。
  • 2、基于REST的Web Service :REST(Representational State Transfer)是一种软件架构,它使用JSON来描述数据格式,最重要的是HTTP传输协议对REST来说是非必须的。

3-4、REST与SOAP的区别和联系

​ 下面,通过一张表格来对比REST与SOAP之间的异同:

总结

如上所述,我们了解了什么是API,什么是REST API,什么是RESTful API以及Web Service的相关概念。API代表应用程序编程接口,是一种较为宽泛的定义或者说是一种协议,作为软件程序之间相互通信的接口而存在。REST API是API的一个子集,所有的REST API都是API;RESTful API是对REST API架构风格的一种非正式实现方式。API与Web Service都是服务提供者和服务消费者之间的通信手段。最后,为了能够快速的识别API与Web Service之间的差异,将这两种手段的不同之处整理成对照表如下:

鱼与熊掌得兼:Hibernate与Mybatis共存

鱼与熊掌得兼:Hibernate与Mybatis共存

鱼与熊掌得兼:Hibernate与Mybatis共存

很长一段时间,网上有很多关于Hibernate与Mybatis孰优孰劣的争论,两个阵营的人谁也不能说服谁,每个人的理由都很有道理。今天,我分享的主题是:在一个项目中同时使用Hibernate和Mybatis两个ORM框架。

​ 作为一个开发者,没有必要花费过多的时间去证明技术无用论,当你开始指责某个框架垃圾,另外一个框架最好时,隐性的暴露出你对某个框架没有深入的研究,无知的指责对于技术的提升没有任何的帮助。框架本身没有对错一说,只有适合和更适合项目的选择。任何框架都有自身的能力范围,就拿Hibernate和Mybatis这两个ORM框架来说,Hibernate封装了很多有用的API给开发者,降低了操作数据库的难度和复杂度,同时也减少了模板代码的数量,但Hibernate留给开发者可操作的空间相对Mybatis少了很多;Mybatis框架使用起来很灵活,开发者可以自定义查询语句,但增加了模板代码的数量,看起来没有Hibernate那么便捷。两种框架在便捷与灵活两个指标上做出了取舍与妥协,这不能说是框架的错。对于一个框架而言,需要有自身专注的领域和设计愿景,不可能面面俱到。

​ 使用任何一种技术框架,都需要贴合现实的业务需求以及自身的技术能力。当你还没有深入的去了解一门技术或者当前业务需求无法与框架契合时,不要盲目的批判框架的好坏。今天,我不再去对比Hibernate与Mybatis两者之间的优劣,而是给出一个比较中庸的放方案,将两个ORM框架同时整合在一个项目中。

一、准备开发环境

​ 如果你想成功运行本文中的源代码,需要满足一下的几个条件:

  • 1、JDK : JDK 1.8.x及以上版本
  • 2、Maven : Maven 3.x或更高版本
  • 3、Git:版本控制工具,选择一个你喜欢的
  • 4、IDE : 选择你比较喜欢的一个代码编辑器,如STS、IntelliJ IDEA。笔者使用的是IntelliJ IDEA
  • 5、Databases : 选择一个你熟练使用的数据库系统。笔者在本文中使用的是MySQL 5.1.x版本的数据库系统

如需获取本次分享内容的源代码进调试,可以到文章末尾找到源代码仓库连接

二、搭建项目

2-1、引入依赖

​ 为了快速构建项目,笔者采用Spring Boot来构建项目,同时使用加入Spring Data JPA和Mybatis两个ORM框架的依赖包。在此需要特别说明,Hibernate是一个JPA标准的实现,尔Spring Data JPA是一个JPA数据访问抽象,通过Spring Data JPA,可以轻松使用Hibernate框架。

​ 你可以通过Spring Initializer来初始化项目,也可以通过IDEA自带的Spring Initializer功能构建项目,项目构建完成之后,pom.xml文件中的配置如下(包含但不限于文中给出的依赖项):

2-2、定义实体类-User.java

​ 为了演示同时使用Hibernate和Mybatis操作数据库,需要提供一个实体类User.java,代码如下所示:

说明:

在本次演示的项目中,使用到了Lombok插件,它可以让开发者减少模板代码的书写,提高开发速度。@Data注解可以自动生成类属性的getter、setter和toString方法。@NoArgsConstructor会自动为类生成无参构造函数,@AllArgsConstructor则会生成带全部属性的构造函数。

2-3、定义数据持久化接口

​ 在本次课程中,将使用Spring Data JPA来完成写操作,如新增、修改、删除;使用Mybatis来完成读操作,如根据用户ID查询、查询所有的用户等。Spring Data JPA和MyBatis的持久化接口都位于com.ramostear.hm.orm包下,Spring Data JPA的持久化接口相对比较简单,之间继承JpaRepository类即可,代码如下:

说明:因为JPA只负责写操作,所以直接继承并使用JpaRepository提供的API即可,不需要额外的定义其他的接口方法。

​ 下面是Mybatis的映射接口,定义了两个方法:根据ID查询用户信息和查询所有的用户信息。代码如下所示:

说明:

此接口需要注意的地方是@Component@Mapper注解,@Component注解标注此接口后,Spring会自动扫描并配置此类;@Mapper注解是把这个mapper的DAO交由Spring进行管理。

定义完Mybatis 映射接口后,需要提供一个进行数据库查询的xml配置文件。该文件位于resources/mapper文件夹中,UserMapper.xml完整代码如下:

2-4、定义UserService

​ 在UserService接口中,提供三个方法:保存用户信息、根据ID查询用户、查询所有的用户。UserService接口代码如下:

在UserService接口的实现类中,需要同时注入UserRepository和UserMapper两个依赖。我们使用构造函数的方式来注入这两个依赖。代码如下:

说明:

@Transactional注解用于设置每个方法的事务控制方式。@Service注解声明该类是一个服务提供类,且设置了该类被Spring初始化时Bean对象的名称为“userService”。

2-5、定义控制器

​ 最后,提供一个控制器,用于处理客户端的相关请求。在控制器中,提供了三个请求处理方法,分别处理客户端新增用户、根据ID查询用户和查询所有用户的请求。控制器代码如下:

说明:

在本次教程中,为了编码IDEA报警告,所有的依赖注入都采用构造函数的方式注入相关的依赖。

三、配置Hibernate和Mybatis

​ 网络上有很多关于在Spring Boot项目中配置Hibernate和Mybatis的教程,但同时配置Hibernate和Mybatis的文章很少,有一些是通过Java代码的方式对这两个ORM框架进行配置,采用的是多数据源的方法来整合两个框架。其实整合这两个框架没有想象中的那么难,只需要在application.yml或者application.properties配置文件中加入几行代码,就可以完成两个框架的整合。以application.yml配置文件为例,配置代码如下:

是不是很简单,并为没有太多复杂的配置,这是一种较为简单的整合方式。Hibernate和Mybatis共用一个数据源,如果是JPA的忠实粉丝,现在想要使用Mybatis,只需要额外加入mybatis的配置即可。

四、测试

​ 通过以上的几个步骤,整个项目已经搭建完毕,接下来将使用Postman测试工具对Controller的三个方法进行测试,验证两个ORM框架在同一个项目中是否能共存。

​ 首先测试 POST http://localhost/users ,验证Hibernate是否能够成功将用户信息持久化。打开Postman工具,在地址栏输入http://localhost/users请求地址,请求方式选择POST,在Body栏输入如下的信息:

{
    "username":"谭朝红",
    "alias":"ramostear",
    "age":28
}

点击“Send”按钮发送请求,观察服务端响应信息,测试结果如下图所示:

可以看到,服务端成功返回用户信息,且用户ID=3。接下来,我们请求 GET http://localhost/users/3 ,验证Mybatis是否能够成功查询出用户信息,测试结果如下:

通过测试,服务端成功返回了用户ID=3的用户信息:

{
  "id": 3,
  "username": "谭朝红",
  "alias": "ramostear",
  "age": 28
}

由此证明,在同一个项目中,Hibernate和Mybatis均能正常工作,整合方案有效,解决了在同一项目中Hibernate与Mybatis共存的问题。

五、总结

​ 本次课程验证了同时使用Hibernate和Mybatis两个ORM框架的方案可行,且采用了一种比较简单的方式来整合两个框架,摒弃了多数据源的复杂配置,快速实现两个框架并用的需求。

​ 在一个项目中同时使用两个ORM框架有没有实际的意义呢?我的答案是肯定的。同时使用两个ORM框架,两者之间可以相互弥补自身的不足,以达到灵活性和便捷性同时兼顾,另外一方面,在单独使用Mybatis时,开发者需要手动或者借助其他的工具生成数据库表信息,而采用本文的整合方案,Mybatis可以借助JPA自动生成数据库表的能力,从而简化使用Mybatis的步骤。最后,对于一些读多于写的系统,完全可以将这两个框架同时使用,写操作少的模块,可以使用Spring Data JPA快速完成相关功能的实现,对于读操作部分,则可以利用Mybatis来优化查询语句。两者之间的优势互补,能进一步的提升开发效率和系统性能。

本次分享内容的源代码仓库地址:

https://github.com/ramostear/orm-hm.git

5个Spring Event奇技淫巧,你GET到了吗?

5个Spring Event奇技淫巧,你GET到了吗?

5个Spring Event奇技淫巧,你GET到了吗?

前言:

谈到事件,接触过前端或GUI编程(JavaScript,Swing)的同学应该有较深刻印象。如事件源、事件的监听、回调等概念应该耳熟能详。而在Web应用程序中则很少用到事件。但在Web应用程序中,也可以轻松实现面向事件的编程。

1、为什么需要面向事件编程

​ 在本文中,将基于Spring框架来谈谈关于面向事件编程的5个奇技淫巧。在开始主要内容之前,先了解一下为什么需要面向事件编程。首先看一个生活中的一个场景来帮助我们快速的了解面向事件编程所带来的好处。以客户到银行柜台取现为例,在较早以前,银行柜台取现需要一个接一个的排长龙等待柜台业务员处理取现业务,在此期间,客户不能离开队伍去做其他的事情,否则需要重新排队。这好比常规的面向过程的编程,需要依次执行每条逻辑语句,直到所有的语句都执行完毕,方法才返回结果。现在,银行柜台取现多了一台叫号机,需要办理取现业务的客户先通过叫号机领取一张业务号,如果等待人数过多的时候,客户可以先处理自身的事情,直到柜台叫到自己的号时,才到柜台办理取现业务。这就是一个典型(不太严谨)面向事件的处理过程。首先,客户通过叫号机注册一个取现的事件,此时的叫号机,相当于事件发布器(事件注册中心),客户相当于事件源,当柜台收到有客户需要取现的消息时,就会广播提示客户办理取现业务,柜台就相当于一个事件监听器。

​ 如上所述,面向事件的编程(也称作事件驱动编程)是基于对消息信号的反应的编程。面向事件编程需要满三个条件:

  • 1、事件源:消息的源头,如点击一个按钮、访问某个页面、新增一条数据等。
  • 2、事件注册中心:事件注册中心用于存储和发布事件。
  • 3、事件监听器:用于接收特定的消息,并作出对应的反应。

下面通过一张图更为直观的了解面向事件编程的基本原理:

图 1-1 事件处理机制

2、Spring Events

​ Spring Events 是Spring Framework的一部分,但在应用程序中并不经常使用,甚至被忽略。然而,Spring的Application拥有强大的事件发布并注册事件监听器的能力,它拥有一套完整的事件发布与处理机制。Spring 4.1开始引入@EventListener注解来监听具体类型的事件,它的出现,消除了使用Spring定义一个事件监听器的复杂操作。仅仅通过一个简单的注解,就可以完成一个监听器的定义,你不需要额外的进行其他的配置;这简直太棒了。在Spring Framework中,事件的发布是由ApplicationContext提供的,如果想完成一个完整的面向事件(也称为事件驱动编程)编程,你需要遵循以下三个基本的原则:

  • 1、定义一个事件,且扩展ApplicationEvent
  • 2、事件发布者应该注入一个ApplicationEventPublisher对象
  • 3、监听器实现ApplicationListener接口或者使用@EventListener注解

在开始介绍面向事件编程的具体实施细节之前,先通过一张类图了解一下Spring的事件机制设计类图:

图 2-1 Spring Event 类图

​ 通过类图,先快速对Spring Event事件类图做一个简单的介绍:

  • 1、ApplicationEventPublisher是Spring的事件发布接口,所有的事件都是通过该接口提供的publishEvent方法发布事件的。
  • 2、ApplicationEventMulticaster是Spring事件广播器,它将提供一个SimpleApplicationEventMulticaster实现类,如果没有提供任何可用的自定义广播器,Spring将采用默认的广播规则。
  • 3、ApplicationListener是Spring事件监听接口,所有的事件监听器都需要实现此接口,并通过onApplicationEvent方法处理具体的逻辑。
  • 4、在Spring Framework中,ApplicationContext本身就具备监听器的能力,我们也可以通过ApplicationContext发布先关的事件。

​ 通过分析,可以看到:当一个事件通过ApplicationEventPublisher发布出去之后,ApplicationEventMulticaster广播器就会开始工作,它将去ApplicationContext中寻找对应的事件监听器(ApplicationListener),并执行对应监听器中的onApplicationEvent方法,从而完成事件监听的全部逻辑。

3、自定义Spring Event

​ 本次内容将使用Spring Boot 2.1.5快速搭建所需要的环境。首先将演示如何自定义一个事件,并监听该事件且对监听到的事件做出反应。

​ 我们将使用IntelliJ IDEA创建一个Web项目,并在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/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.1.5.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>com.ramostear</groupId>
    <artifactId>spring-boot-event</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>spring-boot-event</name>
    <description>Demo project for Spring events</description>

    <properties>
        <java.version>1.8</java.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-jpa</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-devtools</artifactId>
            <scope>runtime</scope>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>5.1.47</version>
            <scope>runtime</scope>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>