SpringData简单梳理
文章目录
作者:李晶晶;
日期;2021年3月12日;
引
其实好几天之前这几个demo就写好了,但一直没时间写篇文档来总结一下,事情有点多,人也有点累。
SpringData是一个持久层的通用解决方案,目的是统一不同持久层的操作api。
原来比如MySql,redis他们需要提供一套api让Java程序员使用。后来SpringData就给出了一套规范(一组接口),让那些厂家实现这组规范。这样对于程序员来说,无论持久层采取什么方案,代码写起来都差不多。
这篇文档会给出使用SpringDataJpa、SpringDataRedis、SpringDataElasticSearch、SpringDataMongoDB的简单使用。
demo都是基于SpringBoot的而不是Spring。
Jpa
Jpa也是一套规范,这里就不展开说了,它实现了不同数据库之间的切换,代码不用动。因为它根本就不在代码里写sql(不同于MyBatis)。
Jpa的实现厂家有很多,但是比较常用的就是Hibernate的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>
配置
spring:
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
name: defaultDataSource
url: jdbc:mysql://localhost:3306/test2?useUnicode=true&characterEncoding=UTF-8&useSSL=false&serverTimezone=Asia/Shanghai
username: root
password: lijingjing
jpa:
hibernate:
ddl-auto: update
show-sql: true
实体类
实体类与表结构的对应可以通过注解配置。
package com.ljj.pojo;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.Table;
@Entity
@Table(name = "user")
public class User {
@Id//主键
@GeneratedValue(strategy = GenerationType.IDENTITY)//自增
@Column(name = "id")//若字段名与属性名一致可省略
private Integer id;
private String name;
private Integer age;
private String sex;
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
public String getSex() {
return sex;
}
public void setSex(String sex) {
this.sex = sex;
}
@Override
public String toString() {
return "User [id=" + id + ", name=" + name + ", age=" + age + ", sex=" + sex + "]";
}
}
Dao
package com.ljj.dao;
import org.springframework.data.jpa.repository.JpaRepository;
import com.ljj.pojo.User;
public interface UserDao extends JpaRepository<User, Integer>{
}
没错写一个空接口就行了,JpaRepository的最上面的那个接口是Repository注解,所以可以看到我们也不用自己来注入。
注意,我们是不需要写这个接口的实现的,实现都由Hibernate做好了的。
这里有一条继承关系:JpaRepository -> PagingAndSortingRepository -> CrudRepository -> Repository
.
每个接口里都由很多方法可以使用。
JpaRepository中的泛型:第一个是实体类,第二个是主键类型;
我们其实也可以在接口中定义一些别的方法,只要按照Jpa的规则来就行,具体规则这里就不细说了,百度一下。
测试案例
下面试一下简单操作的测试demo
package com.ljj;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import com.ljj.dao.UserDao;
import com.ljj.pojo.User;
@SpringBootTest
class SpringDataJpaApplicationTest {
@Autowired
private UserDao userDao;
@Test
void select() {
// 查询全部
List<User> list = userDao.findAll();
list.forEach(user -> System.out.println(user));
// 查询单个
Optional<User> u = userDao.findById(1);
System.out.println(u.get());
}
@Test
void insert() {
// 插入一条
User u = new User();
u.setAge(12);
u.setSex("man");
u.setName("李晶晶");
userDao.save(u);
// 插入(没id)或修改(有id)多条
User u1 = new User();
u1.setAge(13);
u1.setSex("man");
u1.setName("李晶晶");
User u2 = new User();
u2.setAge(13);
u2.setSex("man");
u2.setName("李晶晶");
User u3 = new User();
u3.setAge(13);
u3.setSex("man");
u3.setName("李晶晶");
List<User> list = new ArrayList<User>();
list.add(u1);
list.add(u2);
list.add(u3);
userDao.saveAll(list);
}
@Test
void update() {
Optional<User> u = userDao.findById(5);
u.get().setAge(-1);
userDao.save(u.get());
}
@Test
void delete() {
userDao.deleteById(5);
}
}
可以看到我全程没写过一条sql,所以对于一些简单的业务,Jpa开发起来是真的快。
当然如果业务很复杂,其实没有直接写sql来的方便。
Repository
可以看做是SpringData操作持久层的一种方式,但其实还有另一种方式Template
,在接下的操作Redis中我们可以看到。
Redis
Redis一般用来做缓存,使用SpringData操作Redis同样是十分方便的。
依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
配置
spring:
redis:
database: 0 #默认就是0
host: 127.0.0.1
password: #默认就是空
port: 6379
实体类
package com.ljj.pojo;
import java.io.Serializable;
public class Person implements Serializable{
private static final long serialVersionUID = 1L;
private int age;
private String name;
private String info;
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getInfo() {
return info;
}
public void setInfo(String info) {
this.info = info;
}
@Override
public String toString() {
return "Person [age=" + age + ", name=" + name + ", info=" + info + ", getAge()=" + getAge() + ", getName()="
+ getName() + ", getInfo()=" + getInfo() + ", getClass()=" + getClass() + ", hashCode()=" + hashCode()
+ ", toString()=" + super.toString() + "]";
}
}
Redis就没什么表结构了,反正就那么几种数据类型。
要注意的是实体类需要继承Serializable
结构,方便将对象序列化到Redis中。
测试案例
操作str
package com.ljj;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.StringRedisSerializer;
@SpringBootTest
public class StrTest {
@Autowired
private RedisTemplate<Object, Object> template;
@BeforeEach
void init() {
System.out.println("初始化");
template.setKeySerializer(new StringRedisSerializer());
template.setValueSerializer(new StringRedisSerializer());
}
/**
* set操作
*/
@Test
void setStr() {
// 插入操作
template.opsForValue().set("ccc", new String("aaa"));
// 保存10秒
template.opsForValue().set("name", "ljj", 10, TimeUnit.SECONDS);
// 替换部分字符串
template.opsForValue().set("ccc", "bc", 1);
// 有了就不存储,没有才插入
template.opsForValue().setIfAbsent("name", "muziliuri");
// 批量插入
Map<String, String> map = new HashMap<String, String>();
for (int i = 0; i < 5; i++) {
map.put(i + "", i + "");
}
template.opsForValue().multiSet(map);
// 追加
template.opsForValue().append("性别", "男");
}
/**
* get操作
*/
@Test
void getStr() {
// 获取操作
System.out.println(template.opsForValue().get("ccc"));
// 获取子串(闭区间)
System.out.println(template.opsForValue().get("ccc", 0, 1));
// 批量获取
List<Object> list = new ArrayList<Object>();
for (int i = 0; i < 5; i++) {
list.add(i + "");
}
List<Object> values = template.opsForValue().multiGet(list);
for (Object value : values) {
System.out.println(value);
}
// 获取值的长度
System.out.println(template.opsForValue().size("ccc"));
}
/**
* 自增自减操作
*/
@Test
void incre() {
// 自增1
template.opsForValue().increment("0");
// 自增5
template.opsForValue().increment("1", 5);
// 自减1
template.opsForValue().decrement("2");
// 自减5
template.opsForValue().decrement("3", 5);
}
}
操作hash
package com.ljj;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.data.redis.core.HashOperations;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.JdkSerializationRedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;
import com.ljj.pojo.Person;
@SpringBootTest
public class HashTest {
@Autowired
private RedisTemplate<String, String> template;
private HashOperations<String, String, Person> ops;
@BeforeEach
void init() {
template.setKeySerializer(new StringRedisSerializer());
template.setHashKeySerializer(new StringRedisSerializer());
template.setHashValueSerializer(new JdkSerializationRedisSerializer());
this.ops = template.opsForHash();
}
@Test
void put() {
Person p = new Person();
p.setAge(12);
p.setInfo("程序员");
p.setName("ljj");
// 保存
ops.put("people", "me", p);
// ops.putAll(key, m);批量保存
// ops.putIfAbsent(key, hashKey, value); 无责保存,有则不变
}
@Test
void get() {
// 是否有hashkey
System.out.println(ops.hasKey("people", "me"));
// 获取值
System.out.println(ops.get("people", "me"));
// 获取所有键
Set<String> keys = ops.keys("people");
// 获取所有值
List<Person> values = ops.values("people");
// 获取键值对
Map<String, Person> entries = ops.entries("people");
}
@Test
void delete() {
ops.delete("people", "me");
}
}
操作list
package com.ljj;
import java.util.List;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.data.redis.core.ListOperations;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.ValueOperations;
@SpringBootTest
public class ListTest {
@Autowired
private RedisTemplate<String, String> template;
private ListOperations<String, String> ops;
@BeforeEach
void init() {
this.ops = template.opsForList();
}
@Test
void add() {
// 添加一个
ops.leftPush("brand", "dell");
// 添加多个
ops.leftPushAll("brand", "hp", "lenovo");
}
@Test
void find() {
// 查询指定个数(负数表示从右往左查)
ops.index("brand", 1);
// 全找一遍
List<String> range = ops.range("brand", 0, -1);
range.forEach(item -> System.out.println(item));
}
@Test
void delete() {
// 弹出操作
String rightPop = ops.rightPop("brand");
System.out.println(rightPop);
}
}
操作set
package com.ljj;
import java.util.List;
import java.util.Set;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.SetOperations;
@SpringBootTest
public class SetTest {
@Autowired
private RedisTemplate<String, String> template;
private SetOperations<String, String> ops;
@BeforeEach
void init() {
this.ops = template.opsForSet();
}
@Test
void add() {
// 添加
ops.add("Integer", "一", "二", "一");
}
@Test
void find() {
// 查询所有
String key = "Integer";
Set<String> members = ops.members(key);
members.forEach(e -> System.out.println(e));
// 随机获取一个
String randomMember = ops.randomMember("Integer");
System.out.println(randomMember);
// 随机获取指定个元素
List<String> randomMembers = ops.randomMembers("Integer", 2);
randomMembers.forEach(e -> System.out.println(e));
}
@Test
void delete() {
Long remove = ops.remove("Integer", "一", "二", "三");
System.out.println(remove);
}
}
操作zset
package com.ljj;
import java.util.Set;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.ZSetOperations;
@SpringBootTest
public class ZsetTest {
@Autowired
private RedisTemplate<String, String> template;
private ZSetOperations<String, String> ops;
@BeforeEach
void init() {
this.ops = template.opsForZSet();
}
@Test
void add() {
// 添加成员
ops.add("stu", "a", 23);
ops.add("stu", "b", 24);
ops.add("stu", "c", 25);
// 增加元素score
ops.incrementScore("stu", "a", 3);
}
@Test
void find() {
// 查询score
Double score = ops.score("stu", "b");
System.out.println(score);
// 查询元素拍名
Long rank = ops.rank("stu", "c");
System.out.println(rank);
// 根据拍名区间查询元素
Set<String> range = ops.range("stu", 0, -1);
range.forEach(e -> System.out.println(e));
}
@Test
void delete() {
}
}
通用操作
package com.ljj;
import java.util.ArrayList;
import java.util.List;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.StringRedisSerializer;
@SpringBootTest
public class AllTest {
@Autowired
private RedisTemplate<Object, Object> template;
@BeforeEach
void init() {
System.out.println("初始化");
template.setKeySerializer(new StringRedisSerializer());
template.setValueSerializer(new StringRedisSerializer());
}
/**
* 删除操作
*/
@Test
void delete() {
// 删除
template.delete("ccc");
// 批量删除
List<Object> list = new ArrayList<Object>();
list.add("1");
list.add("2");
list.add("3");
template.delete(list);
}
}
这就是SpringData的另一种方案Template
,他将一切操作都封装到一个template中,同样非常的方便。
ElasticSearch
es是一个全文搜索引擎,也能用来做存储,这里的案例使用Repository
的方式来操作。
依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-elasticsearch</artifactId>
</dependency>
配置
spring:
elasticsearch:
rest:
uris: http://localhost:9200
这是最新的推荐的端口9200。
之前一直是用9300的,那个不是http协议的。现在SpringData支持了es的http端口,官网推荐用这个了。
实体类
@Document(indexName = "people")
标明了索引名称;(在es7版本及以后,不再有type的概念,一个索引下就只有一个类型_doc
).
@Field(type = FieldType.Keyword)
表示了该字段的类型是keyword(不支持分词)。
package com.ljj.pojo;
import org.springframework.data.annotation.Id;
import org.springframework.data.elasticsearch.annotations.Document;
import org.springframework.data.elasticsearch.annotations.Field;
import org.springframework.data.elasticsearch.annotations.FieldType;
@Document(indexName = "people")
public class People {
@Id
private Integer id;
@Field(type = FieldType.Keyword)
private String name;
@Field(type = FieldType.Keyword)
private String identity;
@Field(type = FieldType.Integer)
private Integer age;
@Field(type = FieldType.Double)
private Double salary;
@Field(type = FieldType.Text)
private String info;
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getIdentity() {
return identity;
}
public void setIdentity(String identity) {
this.identity = identity;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
public Double getSalary() {
return salary;
}
public void setSalary(Double salary) {
this.salary = salary;
}
public String getInfo() {
return info;
}
public void setInfo(String info) {
this.info = info;
}
@Override
public String toString() {
return "People [id=" + id + ", name=" + name + ", identity=" + identity + ", age=" + age + ", salary=" + salary
+ ", info=" + info + "]";
}
}
Dao
package com.ljj.dao;
import java.util.List;
import org.springframework.data.elasticsearch.repository.ElasticsearchRepository;
import com.ljj.pojo.People;
public interface PeopleDao extends ElasticsearchRepository<People, Integer> {
//info字段的类型是text,所以这个方法其实是全文检索
List<People> findByInfo(String info);
}
测试案例
package com.ljj;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import com.ljj.dao.PeopleDao;
import com.ljj.pojo.People;
@SpringBootTest
class SpringDataElasticSearchApplicationTests {
@Autowired
private PeopleDao peopleDao;
@Test
void test() {
Iterable<People> people = peopleDao.findAll();
people.forEach(peo -> System.out.println(peo));
System.out.println(peopleDao.findById(1));
System.out.println(peopleDao.findByInfo("not"));
}
}
MongoDB
依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-mongodb</artifactId>
</dependency>
配置
spring:
data:
mongodb:
uri: mongodb://localhost:27017/study
实体类
package com.ljj.pojo;
import org.springframework.data.annotation.Id;
import org.springframework.data.mongodb.core.mapping.Document;
@Document(collation = "people")
public class People {
@Id
private Integer id;
private String name;
private String identity;
private Double salary;
private String info;
private Integer age;
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getIdentity() {
return identity;
}
public void setIdentity(String identity) {
this.identity = identity;
}
public Double getSalary() {
return salary;
}
public void setSalary(Double salary) {
this.salary = salary;
}
public String getInfo() {
return info;
}
public void setInfo(String info) {
this.info = info;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
@Override
public String toString() {
return "People [id=" + id + ", name=" + name + ", identity=" + identity + ", salary=" + salary + ", info="
+ info + ", age=" + age + "]";
}
}
Dao
package com.ljj.dao;
import org.springframework.data.mongodb.repository.MongoRepository;
import com.ljj.pojo.People;
public interface PeopleDao extends MongoRepository<People, Integer>{
}
测试案例
package com.ljj;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import com.ljj.dao.PeopleDao;
import com.ljj.pojo.People;
@SpringBootTest
class SpringDataMongoDbApplicationTests {
@Autowired
private PeopleDao peopleDao;
@Test
void save() {
People p = new People();
p.setId(1);
p.setAge(15);
p.setIdentity("student");
p.setInfo("a good man");
peopleDao.save(p);
}
@Test
void find() {
System.out.println(peopleDao.findById(1));
}
}
跟上面的都差不多是吧,这就是SpringData的魅力。