4、SpringBoot整合MyBatis、Jpa、Redis
4.1、整合MyBatis
POM
<dependencies>
<!--lombok插件-->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
<!--整合MyBatis依赖-->
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>1.3.2</version>
</dependency>
<!--mysql驱动注册-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
<!--测试相关的依赖包-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>org.junit.vintage</groupId>
<artifactId>junit-vintage-engine</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
配置
# Mysql数据库连接配置 : com.mysql.cj.jdbc.Driver
spring.datasource.url=jdbc:mysql://localhost:3306/demo?serverTimezone=UTC
spring.datasource.username=root
spring.datasource.password=root
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
#开启驼峰命名匹配映射
mybatis.configuration.map-underscore-to-camel-case=true
#配置mybatis的xml映射配置文件路径
mybatis.mapper-locations=classpath:mapper/*.xml
#配置mybatis映射配置文件中实体类别名
mybatis.type-aliases-package=com.springboot.demo.pojo
启动类
@SpringBootApplication
@MapperScan("com.springboot.demo.mapper")
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class,args);
}
}
Pojo以及Mapper
@Data
public class Person {
private Integer id;
private String name;
private Integer petId;
}
@Mapper
public interface PersonMapper {
@Select("select * from person where id = #{id}")
Person getById(Integer id);
Person selectById(Integer id);
}
为了演示注解和xml方式都进行了配置
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.springboot.demo._01mybatisDemo.mapper.PersonMapper">
<select id="selectById" resultType="com.springboot.demo._01mybatisDemo.pojo.Person">
select * from person where id =#{id}
</select>
</mapper>
总结 :在使用Mapper接口的时候可以在接口上贴@Mapper或者在启动类上贴@MapperScan。
4.2整合JPA
POM
<dependencies>
<!--lombok插件-->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
<!--SpringBoot整合jpa的包-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<!--mysql驱动注册-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
<!--测试相关的依赖包-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>org.junit.vintage</groupId>
<artifactId>junit-vintage-engine</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
配置
# Mysql数据库连接配置 : com.mysql.cj.jdbc.Driver
spring.datasource.url=jdbc:mysql://localhost:3306/demo?serverTimezone=UTC
spring.datasource.username=root
spring.datasource.password=admin
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
Pojo以及接口
@Data
@Entity
@Table(name = "person")
public class Person {
@Id //表明映射主键id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id;
@Column
private String name;
@Column(name = "pet_id")
private Integer petId;
}
public interface PersonRepository extends CrudRepository<Person,Integer> {
}
4.3整合Redis
POM
<dependencies>
<!--lombok插件-->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
<!--redis整合包-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<!--测试相关的依赖包-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>org.junit.vintage</groupId>
<artifactId>junit-vintage-engine</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.74</version>
</dependency>
</dependencies>
配置
- #redis服务器地址
- spring.redis.host=127.0.0.1
- #redis服务器连接端口
- spring.redis.port=6379
- #redis服务器连接密码
- spring.redis.password=
使用
方式1通过CRUD的方式使用
@AllArgsConstructor
@NoArgsConstructor
@Data
//指定存储在redis中的数据类型
@RedisHash(value = "dog")
public class Dog {
@Id // 用来标识实体类主键 字符串形式的hashkey标识唯一的实体类对象id
private String id;
@Indexed//生成二级索引
private String name;
}
public interface DogRepository extends CrudRepository<Dog,String> {
List<Dog> findByName(String name);
}
测试
@Autowired
private DogRepository dogRepository;
@Test
public void testDogRepository() throws Exception {
dogRepository.save(new Dog("dog:1","旺财"));
List<Dog> dogs = dogRepository.findByName("旺财");
System.out.println(dogs);
}
这种是将Redis作为DB来使用不推荐使用。
方式二
@Autowired
private RedisTemplate<String,String> redisTemplate;
@Test
public void testRedisTemplate() throws Exception {
redisTemplate.opsForValue().set("helloDog", JSONObject.toJSONString(new Dog("1","2")));
String helloDog = redisTemplate.opsForValue().get("helloDog");
Dog dog = JSONObject.parseObject(helloDog, Dog.class);
System.out.println(dog);
}
5、SprinBoot整合Thymeleaf模版引擎
5.1 SpringBoot对模板的支持
SpringBoot框架支持很多种模板,(FreeMarker、Thymeleaf、Mustache等),大大方便了前端的开发。
SpringBoot对Jsp不太支持,并且官方没有提供整合的配置,原因:
1、SpringBoot使用的是嵌入式Servlet web容器,默认使用jar的打包方式,jar包本身不支持jsp模板。
2、如果使用Undertow嵌入式容器部署SpringBoot项目的话,不支持Jsp模板。
3、SpringBoot默认提供了一个处理请求路径"/error"的统一错误处理器,来返回具体的异常信息,如果使用jsp模板的话无法对默认的错误信息进行覆盖,只能在指定的位置指定错误页面。
5.2 Thymeleaf模板引擎技术
5.2.1 Thymeleaf介绍
Thymeleaf是一种基于服务器端的java模板引擎技术,也是一个优秀的面向Java的XML、XHTML、HTML5页面模板,它具有丰富的标签语言、函数和表达式,在使用Spring Boot框架进行页面设计时,一般会选择Thymeleaf模板。
5.2.2Thymeleaf语法
在HTML页面上使用Thymeleaf标签,Thymeleaf 标签能够动态地替换掉静态内容,使页面动态展示。
常用的标签
th:标签说明th:insert布局标签,替换内容到引入的文件th:replace页面片段包含(类似JSP中的include标签)th:each元素遍历(类似JSP中的c:forEach标签)th:if条件判断,如果为真th:unless条件判断,如果为假th:switch条件判断,进行选择性匹配th:case条件判断,进行选择性匹配th:value属性值修改,指定标签属性值th:href用于设定链接地址th:src用于设定链接地址th:text用于指定标签显示的文本内容
标准表达式
1、变量表达式 ${...}
来获取到上下文中变量的值。
Demo:<p th:text="${title}">这是标题</p>
Thymeleaf内置的上下文对象有:
ctx上下文对象vars上下文变量locale上下文区域设置request(仅限Web Context)HttpServletRequest对象response(仅限Web Context)HttpServletResponse对象session(仅限Web Context)HttpSession对象servletContext(仅限Web Context)ServletContext对象
2、选择变量表达式 *{...}
也是获取到上下问的变量只不过是从业务对象中获取
Demo:
<div th:object="${book}"> <p>titile: <span th:text="*{title}">标题</span>.</p></div>
3、消息表达式 #{...}
消息表达式#{...}主要用于Thymeleaf模板页面国际化内容的动态替换和展示,使用消息表达式#
{...}进行国际化设置时,还需要提供一些国际化配置文件。后边会说明
4、链接表达式 @{...}
链接表达式@{...}一般用于页面跳转或者资源的引入,在Web开发中占据着非常重要的地位,并且使用
也非常频繁,Demo:
<a th:href="@{http://localhost:8080/order/details(orderId=${o.id})}">view</a><a th:href="@{/order/details(orderId=${o.id})}">view</a>
5、片段表达式 ~{...}
片段表达式~{...}用来标记一个片段模板,并根据需要移动或传递给其他模板。其中,最常见的用法是使用th:insert或th:replace属性插入片段。
<div th:insert="~{thymeleafDemo::title}"></div>
thymeleaf Demo
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<link rel="stylesheet" type="text/css" media="all"
href="../../css/gtvg.css" th:href="@{/css/gtvg.css}" />
<title>Title</title>
</head>
<body>
<p th:text="${hello}">HelloWorld</p>
</body>
</html>
5.2.3Thymeleaf使用Demo
POM
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
</dependencies>
配置
#模板缓存
spring.thymeleaf.cache = true
#模板编码
spring.thymeleaf.encoding=utf-8
#应用于模板的模板模式
spring.thymeleaf.mode = HTML5
#指定模板页面存放路径
spring.thymeleaf.prefix = classpath:/templates/
#指定模板页面名称的后缀
spring.thymeleaf.suffix = .html
#配置国际化文件基础名
spring.messages.basename=i18n.login
代码
@Controller
public class LoginController {
@RequestMapping("/toLoginPage")
public String toLoginPage(Model model){
model.addAttribute("currentYear", Calendar.getInstance().get(Calendar.YEAR));
return "login";
}
}
HTML
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1,shrink-to-fit=no">
<title>用户登录界面</title>
<link th:href="@{/login/css/bootstrap.min.css}" rel="stylesheet">
<link th:href="@{/login/css/signin.css}" rel="stylesheet">
</head>
<body class="text-center">
<!-- 用户登录form表单 -->
<form class="form-signin">
<img class="mb-4" th:src="@{/login/img/login.jpg}" width="72" height="72">
<h1 class="h3 mb-3 font-weight-normal" th:text="#{login.tip}">请登录</h1>
<input type="text" class="form-control"
th:placeholder="#{login.username}" required="" autofocus="">
<input type="password" class="form-control"
th:placeholder="#{login.password}" required="">
<div class="checkbox mb-3">
<label>
<input type="checkbox" value="remember-me"> [[#{login.rememberme}]]
</label>
</div>
<button class="btn btn-lg btn-primary btn-block" type="submit" th:text="#{login.button}">登录</button>
<p class="mt-5 mb-3 text-muted">? <span th:text="${currentYear}">2019</span>-<span th:text="${currentYear}+1">2020</span></p>
<a class="btn btn-sm" th:href="@{/toLoginPage(l='zh_CN')}">中文</a>
<a class="btn btn-sm" th:href="@{/toLoginPage(l='en_US')}">English</a>
</form>
</body>
</html>
5.2.4Thymeleaf设置本地化语言
首先需要在resources目录下创建一个文件夹来存放默认的和翻译好的语言
login.properties,login_zh_CN.properties 【默认和中文的都一样】
login.tip=请登录
login.username=用户名
login.password=密码
login.button=登录
login.rememberme=记住我
login_en_US.properties【英文】
login.tip=please sign in
login.username=username
login.password=password
login.button=login
login.rememberme=remmeber me
配置
#配置国际化文件基础名
spring.messages.basename=language.login
Html中修改
<form class="form-signin">
<img class="mb-4" th:src="@{/login/img/login.jpg}" width="72" height="72">
<h1 class="h3 mb-3 font-weight-normal" th:text="#{login.tip}">请登录</h1>
<input type="text" class="form-control"
th:placeholder="#{login.username}" required="" autofocus="">
<input type="password" class="form-control"
th:placeholder="#{login.password}" required="">
<div class="checkbox mb-3">
<label>
<input type="checkbox" value="remember-me"> [[#{login.rememberme}]]
</label>
</div>
<button class="btn btn-lg btn-primary btn-block" type="submit" th:text="#{login.button}">登录</button>
<p class="mt-5 mb-3 text-muted">? <span th:text="${currentYear}">2019</span>-<span th:text="${currentYear}+1">2020</span></p>
<!-- 设置两个超连接传入参数每次点击的时候发送超连接进行刷新 -->
<a class="btn btn-sm" th:href="@{/toLoginPage(setLanguage='zh_CN')}">中文</a>
<a class="btn btn-sm" th:href="@{/toLoginPage(setLanguage='en_US')}">English</a>
</form>
</body>
</html>
设置本地化解析器
public class LanguageLocaleResolver implements LocaleResolver {
@Override
public Locale resolveLocale(HttpServletRequest request) {
String setLanguage = request.getParameter("setLanguage");
Locale locale;
if (!StringUtils.isEmpty(setLanguage)) {
String[] s = setLanguage.split("_");
locale = new Locale(s[0],s[1]);
}else{//为空的情况
String header = request.getHeader("Accept-Language");
String[] split = header.split(",");
String[] split1 = split[0].split("-");
locale = new Locale(split1[0],split1[1]);
}
return locale;
}
@Override
public void setLocale(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Locale locale) {
}
}
配置到Spring容器中进行覆盖 一定要是localeResolver才行
@Bean
public LocaleResolver localeResolver(){
return new LanguageLocaleResolver();
}
总结:在进行本地化配置的时候一定要注意配置文件的命名 文件名_语言_国家
5.3SpringBoot缓存
SpringBoot支持很多缓存组件:
- Generic
- JCache (JSR-107) (EhCache 3、Hazelcast、Infinispan等)
- EhCache 2.x
- Hazelcast
- Infinispan
- Couchbase
- Redis
- Caffeine
- Simple
添加@EnableCaching之后默认会从这个9个缓存组件中去查找,没有的话默认使用Simple缓存组件。
5.3.1默认缓存使用
使用方法:
在启动类上贴上@EnableCaching,在对应的service查询方法上贴上@Cacheable(cacheNames = "name")来开启缓存。
@SpringBootApplication
@EnableCaching
public class CacheApplication {
public static void main(String[] args) {
SpringApplication.run(CacheApplication.class, args);
}
}
//根据id查询person对象爱ing
@Cacheable(cacheNames = "person")
public Person findById(Integer id){
Optional<Person> op = personRepository.findById(id);
return op.isPresent() ? op.get() : null;
}
@Cacheable 注解
属性名说明value/cacheNames指定缓存空间的名称,必配属性。这两个属性二选一使用key指定缓存数据的key,默认使用方法参数值,可以使用SpEL表达式SpEL表达式:#root.mathodName ->获取当前方法名#root.mathod ->当前被调用的方法 #root.target ->当前被调用的目标对象#root.targetClass ->当前被调用的目标对象类#root.args[0] 第一个参数, #root.args[1] 第二个参数... ->当前被调用的方法的参数列表#参数名, 也可以使用 #p0 #a0 0是参数的下标索引 ->根据参数名字取出值#result ->当前方法的返回值keyGenerator指定缓存数据的key的生成器,与key属性二选一使用cacheManager指定缓存管理器cacheResolver指定缓存解析器,与cacheManager属性二选一使用condition指定在符合某条件下,进行数据缓存unless指定在符合某条件下,不进行数据缓存sync指定是否使用异步缓存。默认false
执行原理
Cacheable通过动态代理的方式进行缓存,方法运行之前,先去查询Cache(缓存组件),按照cacheNames指定的名字获取,(CacheManager先获取相应的缓存),第一次获取缓存如果没有Cache组件会自动创建;
@Cacheable在不引入任何的依赖的情况下默认使用的是SimpleCacheConfiguration配置的ConcurrentMapCacheManager缓存组件,将缓存结果存储在cacheMap中。使用一个key,默认就是方法的参数,如果多个参数或者没有参数,是按照某种策略生成的,默认是使用KeyGenerator生成的。
使用SimpleKeyGenerator生成key策略:
参数个数key没有参数new SimpleKey()有一个参数参数值多个参数new SimpleKey(params)
@CachePut注解
目标方法执行完之后生效, @CachePut被使用于修改操作比较多,哪怕缓存中已经存在目标值了,但是这个
注解保证这个方法依然会执行,执行之后的结果被保存在缓存中
@CacheEvict注解
@CacheEvict注解是由Spring框架提供的,可以作用于类或方法(通常用在数据删除方法上),该注解
的作用是删除缓存数据。@CacheEvict注解的默认执行顺序是,先进行方法调用,然后将缓存进行清
除。
5.3.2Redis缓存
1、注解方式使用redis缓存
在使用注解的方式使用redis缓存的时候只需要导入redis的包和配置redis,SpringBoot默认就会走redis缓存,使用方法和默认缓存的使用方法一致。
POM
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
配置
#Redis配置
# Redis服务地址
spring.redis.host=127.0.0.1
# Redis服务器连接端口
spring.redis.port=6379
# Redis服务器连接密码(默认为空)
spring.redis.password=
spring.redis.database=1
# 对基于注解的Redis缓存数据统一设置有效期为1分钟,单位毫秒
spring.cache.redis.time-to-live=60000
原理是在引入redis配置之后,系统直接就会使用RedisCacheConfiguration对缓存进行配置,使用RedisCacheManager来管理缓存,使用方式也是通过@Cacheable等注解来实现的。
修改默认的序列化方式为json方式
通过查看RedisCacheConfiguration 类的源码发现默认是jdk的序列化方式
修改默认的序列化方式
@Configuration
public class RedisAnnoConfig {
@Bean
public RedisCacheManager cacheManager(RedisConnectionFactory redisConnectionFactory) {
// 分别创建String和JSON格式序列化对象,对缓存数据key和value进行转换
RedisSerializer<String> strSerializer = new StringRedisSerializer();
Jackson2JsonRedisSerializer jacksonSeial =
new Jackson2JsonRedisSerializer(Object.class);
// 解决查询缓存转换异常的问题
ObjectMapper om = new ObjectMapper();
om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
jacksonSeial.setObjectMapper(om);
// 定制缓存数据序列化方式及时效
RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig()
.entryTtl(Duration.ofDays(1))//缓存时间为1天
.serializeKeysWith(RedisSerializationContext.SerializationPair
.fromSerializer(strSerializer))
.serializeValuesWith(RedisSerializationContext.SerializationPair
.fromSerializer(jacksonSeial))
.disableCachingNullValues();
RedisCacheManager cacheManager = RedisCacheManager
.builder(redisConnectionFactory).cacheDefaults(config).build();
return cacheManager;
}
}
2、使用RedisTemplate进行缓存
直接注入RedisTemplate进行操作,但是有个问题,注入的redisTemplate在保存数据的时候还是jdk默认的序列化方式因此需要修改序列化方式。RedisAutoConfiguration类来分析。
修改RedisTemplate序列化方式,思路是直接覆盖自动注册的redisTemplate实例。
@Configuration
public class RedisConfig {
@Bean
public RedisTemplate<Object,Object> redisTemplate(
RedisConnectionFactory redisConnectionFactory){
RedisTemplate<Object, Object> redisTemplate = new RedisTemplate<Object, Object>();
redisTemplate.setConnectionFactory(redisConnectionFactory);
//设置序列化方式
Jackson2JsonRedisSerializer jackSonSerializer =
new Jackson2JsonRedisSerializer(Object.class);
redisTemplate.setDefaultSerializer(jackSonSerializer);
//解决查询缓存转换异常问题
ObjectMapper om = new ObjectMapper();
om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
jackSonSerializer.setObjectMapper(om);
// 设置RedisTemplate模板API的序列化方式为JSON
redisTemplate.setDefaultSerializer(jackSonSerializer);
return redisTemplate;
}
}