此文省略Springboot项目的创建以及初始化
学习书籍《深入实践SpringBoot》
1、MySQL
对于传统关系型数据库(如MySQL)来说,SpringBoot使用JPA(Java Persistence API)资源库实现对数据库的操作。JPA是为POJO(Plain Ordinary Java Object)提供持久化的标准规范。
1.1、配置依赖
通过项目模块配置文件pom.xml进行引入JPA和MySQL的依赖
<!-- ... -->
<dependencies>
<!-- mysql-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.21</version>
</dependency>
<!-- jpa-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
</dependencies>
<!-- ... -->
1.2、实体配置
实体-关系模型示例
下面对以上三个实体进行建模:
部门Deparment:
package dbdemo.mysql.entity;
import javax.persistence.*;
@Entity
@Table(name = "department")
public class Department {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
public Department() {
}
/**
*省略 setter 以及getter方法。
*/
}
用户User:
package dbdemo.mysql.entity;
import com.fasterxml.jackson.annotation.JsonBackReference;
import com.fasterxml.jackson.annotation.JsonIgnore;
import org.springframework.format.annotation.DateTimeFormat;
import javax.persistence.*;
import java.util.Date;
import java.util.List;
@Entity
@Table(name = "user")
public class User implements java.io.Serializable{
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private Date createdate;
@ManyToOne
@JoinColumn(name = "did")
@JsonBackReference
private Department deparment;
@ManyToMany(cascade = {}, fetch = FetchType.EAGER)
@JoinTable(name = "user_role",
joinColumns = {@JoinColumn(name = "user_id")},
inverseJoinColumns = {@JoinColumn(name = "roles_id")})
private List<Role> roles;
public User() {
}
/**
*省略 setter 以及getter方法。
*/
}
角色Role:
package dbdemo.mysql.entity;
import javax.persistence.*;
@Entity
@Table(name = "role")
public class Role implements java.io.Serializable{
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
public Role() {
}
/**
*省略 setter 以及getter方法。
*/
}
通过以上三个实体定义,实现了使用Java普通对象(POJO)与数据库表建立映射关系(ORM)。
1.3、JPA实现持久化
JPA是一个接口,继承JPA资源库JpaRepository接口,使用注解@Repository将该接口定义为一个资源库,为其他程序提供存取数据库的功能。
分别对三个实体使用JPA存取数据库实现持久化
package dbdemo.mysql.repository;
import dbdemo.mysql.entity.Department;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
@Repository
public interface DepartmentRepository extends JpaRepository<Department, Long> {
}
package dbdemo.mysql.repository;
import dbdemo.mysql.entity.Role;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
@Repository
public interface RoleRepository extends JpaRepository<Role, Long> {
}
package dbdemo.mysql.repository;
import dbdemo.mysql.entity.User;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
import java.util.Date;
import java.util.List;
@Repository
public interface UserRepository extends JpaRepository<User, Long> {
User findByNameLike(String name);
User readByName(String name);
List<User> getByCreatedateLessThan(Date star);
}
1.4、测试
测试之前,需要保证Mysql中存在你想要访问的数据库,这里假设访问的数据库为test,而用户名设为 root ,密码为 admin。test数据库中是否存在对应的表table并不重要,因为Springboot运行后会帮你创建对应的表结构。
接下来进行实例测试来证明以上设计的正确性:
- 首先配置JPA配置类:JpaConfiguration.class
package dbdemo.mysql.config;
import org.springframework.boot.orm.jpa.EntityScan;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;
import org.springframework.dao.annotation.PersistenceExceptionTranslationPostProcessor;
import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
import org.springframework.transaction.annotation.EnableTransactionManagement;
@Order(Ordered.HIGHEST_PRECEDENCE)
@Configuration
@EnableTransactionManagement(proxyTargetClass = true)
@EnableJpaRepositories(basePackages = "dbdemo.**.repository")
@EntityScan(basePackages = "dbdemo.**.entity")
public class JpaConfiguration {
@Bean
PersistenceExceptionTranslationPostProcessor persistenceExceptionTranslationPostProcessor(){
return new PersistenceExceptionTranslationPostProcessor();
}
}
各注解含义:
- EnableTransactionManagement :启动JPA的事务管理
- EnableJpaRepositories:启用JPA资源库并指定上面定义的接口资源库的位置;
- EntityScan:指定了定义实体的所在位置,对我们所定义的实体进行导入
而在测试中使用的JPA配置类并不是像以上的配置这样简便,我们需要把一些配置参数都包含在用于测试的配置类类定义中,配置内容如下:
package dbdemo.mysql.test;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.dao.annotation.PersistenceExceptionTranslationPostProcessor;
import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
import org.springframework.jdbc.datasource.DriverManagerDataSource;
import org.springframework.orm.jpa.JpaTransactionManager;
import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean;
import org.springframework.orm.jpa.vendor.Database;
import org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.support.TransactionTemplate;
import javax.sql.DataSource;
import java.util.Properties;
@Configuration
@EnableJpaRepositories(basePackages = "dbdemo.**.repository")
public class JpaConfiguration {
@Bean
PersistenceExceptionTranslationPostProcessor persistenceExceptionTranslationPostProcessor(){
return new PersistenceExceptionTranslationPostProcessor();
}
@Bean
public DataSource dataSource() {
DriverManagerDataSource dataSource = new DriverManagerDataSource();
dataSource.setDriverClassName("com.mysql.cj.jdbc.Driver");
dataSource.setUrl("jdbc:mysql://localhost:3306/test?characterEncoding=utf8");
dataSource.setUsername("root");
dataSource.setPassword("admin");
return dataSource;
}
@Bean
public LocalContainerEntityManagerFactoryBean entityManagerFactory() {
LocalContainerEntityManagerFactoryBean entityManagerFactoryBean = new LocalContainerEntityManagerFactoryBean();
entityManagerFactoryBean.setDataSource(dataSource());
entityManagerFactoryBean.setPackagesToScan("dbdemo.mysql.entity");
entityManagerFactoryBean.setJpaProperties(buildHibernateProperties());
entityManagerFactoryBean.setJpaVendorAdapter(new HibernateJpaVendorAdapter() {{
setDatabase(Database.MYSQL);
}});
return entityManagerFactoryBean;
}
protected Properties buildHibernateProperties()
{
Properties hibernateProperties = new Properties();
hibernateProperties.setProperty("hibernate.dialect", "org.hibernate.dialect.MySQL5Dialect");
hibernateProperties.setProperty("hibernate.show_sql", "true");
hibernateProperties.setProperty("hibernate.use_sql_comments", "false");
hibernateProperties.setProperty("hibernate.format_sql", "true");
hibernateProperties.setProperty("hibernate.hbm2ddl.auto", "update");
hibernateProperties.setProperty("hibernate.generate_statistics", "false");
hibernateProperties.setProperty("javax.persistence.validation.mode", "none");
//Audit History flags
hibernateProperties.setProperty("org.hibernate.envers.store_data_at_delete", "true");
hibernateProperties.setProperty("org.hibernate.envers.global_with_modified_flag", "true");
return hibernateProperties;
}
@Bean
public PlatformTransactionManager transactionManager() {
return new JpaTransactionManager();
}
@Bean
public TransactionTemplate transactionTemplate() {
return new TransactionTemplate(transactionManager());
}
}
- 在Springboot的配置文件application.yml中天界配置来设置数据源和JPA的工作模式:
spring:
datasource:
url: jdbc:mysql://localhost:3306/test?characterEncoding=utf8
username: root
password: admin
jpa:
database: MYSQL
show-sql: true
hibernate:
ddl-auto: update
naming-strategy: org.hibernate.cfg.ImprovedNamingStrategy
properties:
hibernate:
dialect: org.hibernate.dialect.MySQL5Dialect
另外如果是利用application.properties文件进行配置的话,如下:
spring.datasource.url=jdbc:mysql://127.0.0.1:3306/how2java?characterEncoding=UTF-8
spring.datasource.username=root
spring.datasource.password=admin
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
jpa.database=MYSQL
jpa.show-sql=true
jpa.hibernate.ddl-auto=update
jpa.hibernate.naming-strategy=org.hibernate.cfg.ImprovedNamingStrategy
jpa.properties.hibernate=
jpa.properties.dialect=org.hibernate.dialect.MySQL5Dialect
- 接下来编写MysqlTest测试类
- 还有一件事 :
测试用的程序都需要置于项目目录下的src/test/java目录下,否则不能正常进行测试
package dbdemo.mysql.test;
import dbdemo.mysql.entity.Department;
import dbdemo.mysql.entity.Role;
import dbdemo.mysql.entity.User;
import dbdemo.mysql.repository.DepartmentRepository;
import dbdemo.mysql.repository.RoleRepository;
import dbdemo.mysql.repository.UserRepository;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.util.Assert;
import java.util.*;
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = {JpaConfiguration.class})
public class MysqlTest {
private static Logger logger = LoggerFactory.getLogger(MysqlTest.class);
@Autowired
UserRepository userRepository;
@Autowired
DepartmentRepository departmentRepository;
@Autowired
RoleRepository roleRepository;
@Before
public void initData(){
userRepository.deleteAll();
roleRepository.deleteAll();
departmentRepository.deleteAll();
Department department = new Department();
department.setName("开发部");
departmentRepository.save(department);
Assert.notNull(department.getId());
Role role = new Role();
role.setName("admin");
roleRepository.save(role);
Assert.notNull(role.getId());
User user = new User();
user.setName("user");
user.setCreatedate(new Date());
user.setDeparment(department);
List<Role> roles = roleRepository.findAll();
Assert.notNull(roles);
user.setRoles(roles);
userRepository.save(user);
Assert.notNull(user.getId());
}
@Test
public void findPage(){
Pageable pageable = new PageRequest(0, 10, new Sort(Sort.Direction.ASC, "id"));
Page<User> page = userRepository.findAll(pageable);
Assert.notNull(page);
for(User user : page.getContent()) {
logger.info("====user==== user name:{}, department name:{}, role name:{}",
user.getName(), user.getDeparment().getName(), user.getRoles().get(0).getName());
}
}
//@Test
public void test(){
User user1 = userRepository.findByNameLike("u%");
Assert.notNull(user1);
User user2 = userRepository.readByName("user");
Assert.notNull(user2);
List<User> users = userRepository.getByCreatedateLessThan(new Date());
Assert.notNull(users);
}
}
2、Redis
Redis是一种可以持久存储的缓存系统,是高性能的key-value数据库,Redis中的数据主要都存储在内存之中,只有部分存在硬盘之中,它通过json数据实现key-value数据的存储。
Redis官网下载
2.1、配置依赖
SpringBoot中使用Redis,也需要在工程中的Maven配置文件pom.xml中加入spring-boot-starter-redis依赖。
pom.xml中Redis模块的Maven依赖配置:
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-redis</artifactId>
</dependency>
<dependency>
<groupId>com.google.code.gson</groupId>
<artifactId>gson</artifactId>
<version>2.2.4</version>
</dependency>
<dependency>
<groupId>springboot.db</groupId>
<artifactId>mysql</artifactId>
<version>${project.version}</version>
</dependency>
</dependencies>
2.2、Redis服务类
Redis提供的数据类型如下:
- string
- hash
- list
- set 以及 zset
实例使用string字符串类型演示数据的存取操作。
Springboot并没有提供如JPA那样的资源库接口,需要根据Repository的定义写个User的服务类。
package dbdemo.redis.repository;
import com.google.gson.Gson;
import com.google.gson.reflect.TypeToken;
import dbdemo.mysql.entity.User;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Repository;
import org.springframework.util.StringUtils;
import java.util.List;
import java.util.concurrent.TimeUnit;
@Repository
public class UserRedis {
@Autowired
private RedisTemplate<String, String> redisTemplate;
public void add(String key, Long time,User user) {
Gson gson = new Gson();
redisTemplate.opsForValue().set(key, gson.toJson(user), time, TimeUnit.MINUTES);
}
public void add(String key, Long time, List<User> users) {
Gson gson = new Gson();
redisTemplate.opsForValue().set(key, gson.toJson(users), time, TimeUnit.MINUTES);
}
public User get(String key) {
Gson gson = new Gson();
User user = null;
String userJson = redisTemplate.opsForValue().get(key);
if(!StringUtils.isEmpty(userJson))
user = gson.fromJson(userJson, User.class);
return user;
}
public List<User> getList(String key) {
Gson gson = new Gson();
List<User> ts = null;
String listJson = redisTemplate.opsForValue().get(key);
if(!StringUtils.isEmpty(listJson))
ts = gson.fromJson(listJson, new TypeToken<List<User>>(){}.getType());
return ts;
}
public void delete(String key){
redisTemplate.opsForValue().getOperations().delete(key);
}
}
该服务类实现存取对象User以及由User组成的列表List,以及提供删除User对象的方法。这些方法都是使用RedisTemplate实现的。
2.3、数据格式转换
Redis中没有表结构的概念,想实现MySql数据库中表数据在Redis中存储需要进行转换,使用Json格式文本在Redis和Java对象之间交换数据。Java中使用Gson将类对象转换为Json格式进行存储;去除数据时,再将Json格式的数据转换为Java对象。
声明一个Redis配置类,对RedisTemplate存储的字符串进行Json格式的初始配置,使RedisTemplate能够正确调用。
RedisConfig.class
package dbdemo.redis.config;
import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.cache.annotation.CachingConfigurerSupport;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
@Configuration
public class RedisConfig {
@Bean
public RedisTemplate<String, String> redisTemplate(
RedisConnectionFactory factory) {
StringRedisTemplate template = new StringRedisTemplate(factory);
Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);
ObjectMapper om = new ObjectMapper();
om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
jackson2JsonRedisSerializer.setObjectMapper(om);
template.setValueSerializer(jackson2JsonRedisSerializer);
template.afterPropertiesSet();
return template;
}
}
2.4、Redis测试
启动Redis服务器后,在工程的配置文件application.yml中配置连接Redis服务器的一些参数,如下:
spring:
redis:
# database: 1
host: 127.0.0.1
port: 6379
pool:
max-idle: 8
min-idle: 0
max-active: 8
max-wait: -1
在test/java文件夹下编写JUnit测试程序,测试在Redis服务器中存取数据。
package dbdemo.redis.test;
import dbdemo.mysql.entity.Department;
import dbdemo.mysql.entity.Role;
import dbdemo.mysql.entity.User;
import dbdemo.redis.repository.UserRedis;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.util.Assert;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = {RedisConfig.class, UserRedis.class})
public class RedisTest {
private static Logger logger = LoggerFactory.getLogger(RedisTest.class);
@Autowired
UserRedis userRedis;
@Before
public void setup(){
Department deparment = new Department();
deparment.setName("开发部");
Role role = new Role();
role.setName("admin");
User user = new User();
user.setName("user");
user.setCreatedate(new Date());
user.setDeparment(deparment);
List<Role> roles = new ArrayList<>();
roles.add(role);
user.setRoles(roles);
userRedis.delete(this.getClass().getName()+":userByname:"+user.getName());
userRedis.add(this.getClass().getName()+":userByname:"+user.getName(), 10L, user);
}
@Test
public void get(){
User user = userRedis.get(this.getClass().getName() + ":userByname:user");
Assert.notNull(user);
logger.info("======user====== name:{}, deparment:{}, role:{}",
user.getName(), user.getDeparment().getName(), user.getRoles().get(0).getName());
}
}