【开发技术】SpingBoot数据库与持久化技术,JPA,MongoDB,Redis

sSpringBoot

内容管理

    • 使用JdbcTemplate访问RDB
      • Spring Boot自动初始化数据库 加载schema.sql和data.sql
      • JdbcTemplate.queryForObject
      • 使用RowMapper映射实体
      • 使用BeanPropertyRowMapper映射
      • jdbcTemplate.queryForList
      • NamedParameterJdbcTemplate可以使用有含义的占位符取代 ?
      • jdbcTemplate.update()
    • JPA 【java Persistence API】
      • Spring Data JPA
      • 基于JpaRepository和CrudRespository接口查询
      • 基于JpaSpecificationExecutor接口查询
      • 基于JPQL和SQL, 依赖注解@Query:1st_place_medal:
      • 多表连接
        • @OneToOne 一对一
        • @OneToMany 和@ManyToOne
        • @ManyToMany
      • 级联操作cascade
      • 加载类型fetchType
    • Spring Data MongoDB
      • 安装MongoDB
      • 引入stater依赖
      • 继承AbstractMongoClientConfiguration配置
      • 使用MongoTemplate访问MongoDB
      • 创建MongoDB的实体类@Document
        • insert(T objectToSave, String collectionName) 用于在初始化时插入到数据库的集合中
        • save(T objectToSave) 保存实体,插入或者更新
        • updateFirst(Query query,UpdateDefintion update,Class EntityClass) 更新查询到的第一条记录,返回值为UpdateResult更新后的结果
        • updateMulti((Query query,UpdateDefintion update,Class EntityClass)批量更新
        • findAndModify((Query query,UpdateDefintion update,Class EntityClass)更新第一条记录,返回值为更新前记录
        • upsert((Query query,UpdateDefintion update,Class EntityClass) 类似与save,但是这是根据query的结果
      • 使用MongoRepository访问MongoDB【和Redis有所区别】
        • saveAll(Iterable s> entities) 保存集合中所有的对象到MongoDB中,默认是类名同名的Collection中
        • exists(Example s> example) 判断是否存在与Example匹配的元素
        • findAll(Sort sort) 查询所有记录,按照规则排序,可以直接Sort.Direction.xx获取
        • findAll(Pageable pageable) 查询所有记录分页【海量】
    • Spring Data Redis
      • 引入依赖
        • @EnableCaching 配置类上 启用缓存功能 @Cacheable: 赋予缓存功能,标记方法或者类上,代表该类中所有方法支持缓存
        • 配置使用Jedis为客户端【Pool】
      • 使用RedisRepository访问Redis
        • Redis Hash实体类
      • 使用RedisTepmlate访问Redis
        • 封装自定义redisTemplate 【配置Jedis见上】


SpringBoot开发技术 — 数据库与持久化技术


技术无止境,唯有继续沉淀…之前介绍了SpringBoot的过滤,拦截器,相关的事件,相关的日志,文件等,Restful风格的Http动词,相关的注解,包括相关的@GetMapping等,@RequestParam,@PathVarible,@RequestHeader,@RestController,@RequestBody【前台需要使用相关的JavaScript方法处理表单元素】,@ResponseBody,@Vaildation【@NotNull… 引入Validation依赖】,创建自定义的注解和Validator,全局异常处理@ControllerAdivice,@ExceptionHandler,使用Swagger【使用@Api,@ApiModel…引入springfox依赖】

持久化是项目的基本需求,没有持久化,数据在RAM中断电就会丢失,Spring Boot中应用程序开发所使用的持久化,依赖各种类型的数据库、对应的客户端和相关的持久层框架【Redis,MongoDB】

使用JdbcTemplate访问RDB

JDBC是最初访问数据库的手段,是java提供的编写应用程序作为客户端访问数据库的API,JdbcTemplate是对于JDBC的封装(JDBC编程6步 – 有些固定的代码)简化了JDBC的使用,

当请求数据库的需求不复杂的时候,就可以简单使用JDBCTemplate进行数据库的访问,使用JDBCTemplate,就简单的引入相关依赖,在SpringBoot中,需要引入的起步依赖就是Spring-boot-starter-jdbc; 数据库的驱动使用mysql-connecter即可

		
		<dependency>
			<groupId>org.springframework.bootgroupId>
			<artifactId>spring-boot-starter-jdbcartifactId>
		dependency>

		<dependency>
			<groupId>mysqlgroupId>
			<artifactId>mysql-connector-javaartifactId>
		dependency>

Spring Boot自动初始化数据库 加载schema.sql和data.sql

Spring Boot中可以进行相关配置,使得SpringBoot自动从classpath下面加载schema.sql和data.sql,前者决定表结构,后者进行数据的初始化。如果使用不同的数据库,可以在yml中进行配置,配置spring.datasource.platform区分环境,同时建立不同的文件,比如: schema-${platform}.sql和data-…sql

初始化行为通过spring.datasource.initializetion-mode进行控制:

  • DataSourceInitializationMode.ALWAYS : 总是执行初始化操作
  • DataSourceInitializationMode.EMBEDDED: 仅数据源为嵌入式数据库的时候执行
  • DataSourceInitializationMode.NEVER: 从不执行初始化操作

默认情况下,Spring boot使用JDBCTemplate的快速失败功能,也就是当两个脚本出现错误的时候就会导致程序异常;可以通过spring.datasource.contine-on-error来进行调整

上面三项的基础配置从datasource,变为了sql.init【数据源就只是单纯的数据源】

server:
  port: 8081
  servlet:
    context-path: /persistence
spring:
  datasource:
    url: jdbc:mysql://localhost:3306/presis?servertimezone=GMT%2B8
    username: cfeng
    password: a1234567890b
    #当执行schema和data.sql的用户不同时,可以配置相关的username和password
    driver-class-name: com.mysql.cj.jdbc.Driver
  sql:
    init:
      data-locations: classpath:sql/data.sql
      schema-locations: classpath:sql/schema.sql
#      continue-on-error: true
      mode: always  #这就会自动进行初始化,扫描相关的sql文件

现在已经不是在dataSource中定义初始化的相关配置,而是在sql.init下面,需要注意一定要定义data和schema的位置,这样才能够进行自动的扫描,所以其实名称是可以自定义的,但是还是使用默认的名称便于辨认

创建的两个sql脚本demo

##########schema.sql##############
----------------------------------
--- table structure for vehicle
-----------------------------------
DROP TABLE IF EXISTS vehicle;

CREATE TABLE vehicle(
    id INT(11) NOT NULL AUTO_INCREMENT,
    ve_name VARCHAR(255) DEFAULT NULL,
    price DECIMAL(10,2) DEFAULT NULL,
    PRIMARY KEY(id)   <--- 需要注意不要出现语法问题
);

#########data.sql##################
------------------------------
--- Records of vehicle
-----------------------------
INSERT INTO vehicle VALUES (1,"Beanley",2750000.00);
INSERT INTO vehicle VALUEs (2,"Land Rover",1468000.00);
INSERT INTO vehicle VALUES (3,"Lincoin",1580000.00);

有了数据库,那么如何利用JdbcTemplate进行数据的访问呢?

正确配置之后,就需要正确书写Sql脚本,需要注意不要出现语法错误

JdbcTemplate.queryForObject

使用该方法可以执行一条SQL语句得到一个结果对象,结果对象的类型需要在参数中进行声明,需要注意的是这只能执行一条Sql语句,Sql语句就是一个String类型,使用queryForObject需要传入的就是待执行的Sql语句和执行完毕之后的结果类型

这里可以直接使用test模块的assert来进行结果测试

import javax.annotation.Resource;

/**
 * @author Cfeng
 * @date 2022/7/1
 */

@Transactional(rollbackFor = Exception.class) //开启事务
@SpringBootTest//一体化测试,可以加载容器
public class JdbcTemplateTests {

    @Resource
    private JdbcTemplate jdbcTemplate; //spring boot 自动配置的对象,相关的starter中会创建这个对象

    @Test
    public void queryForObjectTest() {
        //查询vehicle表
        String sql = "SELECT COUNT(*) FROM vehicle";
        //获取查询的结果,queryForObject的就是放入相关的sql语句,同时指定返回结果的类类型
        Integer numOfVehicle = jdbcTemplate.queryForObject(sql,Integer.class);
        assert numOfVehicle != null;
        System.out.format("There is %d vehicles in the table",numOfVehicle);
    }
}

使用RowMapper映射实体

JdbcTemplate轻量级,在简单的demo中可以使用,在SpringBoot中就是借助封装的jdbcTemplate对象【starter-jdbc中自动装配】

上面的queryForObjectt的返回值是包装类型,如果要使用自定义类型,直接修改会遇到IncorrectResult…异常,比如返回值如果是Vehicle

@Data
@Accessors(chain = true)
@NoArgsConstructor
public class Vehicle {
    private Integer id;

    private String veName;

    private BigDecimal price;
}

//这里的查询结果如果是这个实体类对象,那么就需要RowMapper映射类的help
    @Test
    public void queryForObjectTest() {
        //查询vehicle表
        String sql = "SELECT * FROM vehicle WHERE id = 1";
        //获取查询的结果,queryForObject的就是放入相关的sql语句,同时指定返回结果的类类型
        Vehicle testVehicle = jdbcTemplate.queryForObject(sql,Vehicle.class);
        assert testVehicle != null;
        System.out.println(testVehicle);

这里的查询结果为自定义类型,但是queryForObject不支持自动化的映射操作,所以需要使用RowMapper

org.springframework.dao.EmptyResultDataAccessException: Incorrect result size

所以这里需要使用Rowmapper进行包裹Vehicle,RowMapper使用JDBC的查询结果集ResultSet,将查询的结果进行set赋值封装为自定义对象

    @Test
    public void queryForObject_WithRowMapper() {
        //RowMapper将自定义类型包裹之后才能被识别
        RowMapper<Vehicle> rowMapper = (ResultSet resultSet, int rowNum) -> {
            return new Vehicle().setId(resultSet.getInt("id")).setVeName(resultSet.getString("veName")).setPrice(resultSet.getBigDecimal("price"));
        };
        String sql = "SELECT * FROM vehicle WHERE id = ?";
        int id = 1;
        Vehicle vehicle = jdbcTemplate.queryForObject(sql,rowMapper,new Object[]{1});
        assert vehicle != null;
        System.out.println(vehicle);
    }

这里还是使用的PreparedStatement使用?作为占位符

使用BeanPropertyRowMapper映射

RowMapper的缺点就是每次查询新的结果都需要重新进行映射操作,因为其与ResultSet有关,当查询的字段名与映射类的属性名一致时,可以使用BeanPropertyRowMapper进行自动映射

其实也就是简化了RowMapper封装结果的过程【使用lambda表达式将ResultSet的结果(使用rowNumber表结果的行数)封装为自定义的结果类型】,BeanPropertyRowMapper在属性一致的时候就可以自动完成这一过程

//需要注意映射类需要有默认或者无参构造器,使用BeanPropertyRowMapper.newInstance(Vehicle.class)即可代替
    @Test
    public void queryForObject_WithRowMapper() {
        //RowMapper将自定义类型包裹之后才能被识别
        RowMapper<Vehicle> rowMapper = (ResultSet resultSet, int rowNum) -> {
            return new Vehicle().setId(resultSet.getInt("id")).setVeName(resultSet.getString("veName")).setPrice(resultSet.getBigDecimal("price"));
        };
        String sql = "SELECT * FROM vehicle WHERE id = ?";
        int id = 1;
        Vehicle vehicle = jdbcTemplate.queryForObject(sql, BeanPropertyRowMapper.newInstance(Vehicle.class),new Object[]{1});
        assert vehicle != null;
        System.out.println(vehicle);
    }

BeanPropertyRowMapper.newInstance(Vehicle.class)就会自动完成包装的过程:

(ResultSet resultSet, int rowNum) -> {
            return new Vehicle().setId(resultSet.getInt("id")).setVeName(resultSet.getString("veName")).setPrice(resultSet.getBigDecimal("price"));
        };

jdbcTemplate.queryForList

queryForObject的查询结果都是单个对象,当查询结果为列表的时候,就应该使用queryForList方法

//相比queryForObject没有什么太大的区别
    @Test
    public void queryForListTest() {
        String sql = "SELECT * FROM vehicle";
        List<Map<String,Object>> result = jdbcTemplate.queryForList(sql);
        assert !result.isEmpty();
        result.forEach(System.out::println);
    }

这里只是简单将查询结果封装为一个List,之后使用forEach打印,结果:

{id=2, ve_name=Land Rover, price=1468000.00}
{id=3, ve_name=Lincoin, price=1580000.00}

NamedParameterJdbcTemplate可以使用有含义的占位符取代 ?

使用简单的JdbcTemplate,其占位符就是 ? ,当占位符过多时,语句含义难以理解,这个时候可以选择使用NamedParameterJdbcTemplate进行代替,可以参照ESQL的 :xxxx

  • 占位符的映射操作就需要使用MapSqlParameterSource操作,参数名需要和SQL语句的占位符保持一致,否则会报错No value supplied for the SQL parameter ‘name’: No value registered
//@SpringBootTest加载容器,可以直接注入NamedParameterJdbcTemplate    
@Test
    public void queryForObject_withNamedJdbcTemplate() {
        String sql = "SELECT * FROM vehicle WHERE ve_name LIKE :ve_name AND price > :price LIMIT 1";
        //映射参数
        MapSqlParameterSource mapSqlParameterSource = new MapSqlParameterSource().addValue("ve_name","L%")
                                                                                 .addValue("price","6000");
        //将参数映射源放入queryForObject中;RowMapper行映射器将资源映射为...
        Vehicle vehicle = namedParameterJdbcTemplate.queryForObject(sql,mapSqlParameterSource,BeanPropertyRowMapper.newInstance(Vehicle.class));
        assert vehicle != null;
        System.out.println(vehicle.toString());
    }

执行结果【当出现…Incorrect异常时,是因为查询的结果为空或者不匹配等问题】

Vehicle(id=2, ve_name=Land Rover, price=1468000.00)

jdbcTemplate.update()

上面的query都是查询方法,当需要更新数据库时,需要使用update方法,包括对记录的增加、删除、修改

    @Test
    public void update_saveVehicle() {
        String sql = "INSERT INTO vehicle(ve_name,price) VALUES(:ve_name,:price)";
        MapSqlParameterSource mapSqlParameterSource = new MapSqlParameterSource().addValue("ve_name","Tesla")
                                                                                 .addValue("price","850000");
        //update的result就是修改的行数
        int result = namedParameterJdbcTemplate.update(sql,mapSqlParameterSource);
        assert result > 0;
        System.out.println(result);
    }

    @Test
    public void update_updateVehicle() {
        //修改操作
        String sql = "UPDATE vehicle SET price = price * 0.5 WHERE ve_name LIKE :ve_name";
        MapSqlParameterSource mapSqlParameterSource = new MapSqlParameterSource().addValue("ve_name","Land%");
        int re = namedParameterJdbcTemplate.update(sql,mapSqlParameterSource);
        assert re > 0;
    }

    @Test
    public void update_deleteVehicle() {
        String sql = "DELETE FROM vehicle WHERE price > :price";
        MapSqlParameterSource mapSqlParameterSource = new MapSqlParameterSource().addValue("price","1500000");
        int ret = namedParameterJdbcTemplate.update(sql,mapSqlParameterSource);
        assert ret > 0;
    }

增删改都是使用的update方法,这里都是用NamedParameterJdbcTemplate使用有含义的占位符,执行的结果都是影响的行数,需要使用MapSqlParameterSource进行参数的映射,之后将映射源加入到update方法中执行sql

JPA 【java Persistence API】

JPA是一种Java持久化规范,可以简化对于ORM技术的整合,结束比如Hibernate、JDO等各自为营的局面,JPA在使用上说就是一种全自动的持久层框架,相关的还有Mybatis-plu,全自动框架不需要再写Sql语句,对于JPA来说,按照JPA规范的相关方法就可以自动调用相关的SQL语句

JPA的操作步骤:

  • 加载配置文件,根据配置串改你实体管理工厂对象
  • 使用实体管理工厂创建实体的管理器
  • 创建事务对象开启事务
  • CRUD操作
  • 提交事务
  • 释放资源

和Mybatis的基本步骤是类似的

public void jpaTest() {
        //加载配置文件,创建实体管理器工厂对象
        EntityManagerFactory factory = Persistence.createEntityManagerFactory("jpaConfig");
        //使用实体管理器工厂创建实体管理器
        EntityManager manager = factory.createEntityManager();
        //获取事务对象,开启事务
        EntityTransaction tx = manager.getTransaction();
        tx.begin();
        //完成CRUD操作
        Vehicle vehicle = new Vehicle();
        vehicle.setVe_name("LinKen");
        //保存使用实体管理器的persist方法
        manager.persist(vehicle);
        //提交事务
        tx.commit();
        //释放资源
        manager.close();
        factory.close();
    }

JPA最主要的几个对象就是EntityManager和其Factory,Mybatis就是SqlSession和其factory

jpa: Presistence ---create(config)-> EntityManagerFactory  ---> EntityManager
mybatis:SqlSessionFactoryBuilder --build(Resouces.getXXXXAsInputStream(config))--> SqlSessionFactory ---> SqlSession

【开发技术】SpingBoot数据库与持久化技术,JPA,MongoDB,Redis_第1张图片

Spring Data JPA

Spring Data JPA是Spring Data中一个,可以轻松实现JPA的存储库。Spring Data JPA主要就是基于JPA进行数据访问层的增强,JPA是规范,Spring Data JPA是一个全自动的数据访问层的框架,使用之后就需要编写Repository接口即可,框架会自动提供对应的实现

【开发技术】SpingBoot数据库与持久化技术,JPA,MongoDB,Redis_第2张图片

该框架是遵守JPA规范的,在JPA规范下提供Repository的实现,提供配置项用以切换具体实现规范的ORM框架

基于JpaRepository和CrudRespository接口查询

基于接口的查询方式,更具接口名的定义自动生成相关的SQL语句的代理实例,不需要手写SQL,并且还提供了基础的CRUD的实现,继承了之后就会默认为@Repository,会自动创建实例,不需要再添加该注解

  • Repostory是Spring Data JPA的核心接口,需要领域实体类domain和实体类entity的ID类型作为类型参数进行管理,该类的作用作为标记接口,捕获要使用的类型和扩展接口子接口
//看看源码
@Indexed
public interface Repository<T, ID> {
}

//再看看JpaRespostory
@NoRepositoryBean
public interface JpaRepository<T, ID> extends PagingAndSortingRepository<T, ID>, QueryByExampleExecutor<T> {
    List<T> findAll();

    List<T> findAll(Sort sort);

    List<T> findAllById(Iterable<ID> ids);

    <S extends T> List<S> saveAll(Iterable<S> entities);

    void flush();

    <S extends T> S saveAndFlush(S entity);

    <S extends T> List<S> saveAllAndFlush(Iterable<S> entities);

    /** @deprecated */
    @Deprecated
    default void deleteInBatch(Iterable<T> entities) {
        this.deleteAllInBatch(entities);
    }

    void deleteAllInBatch(Iterable<T> entities);

    void deleteAllByIdInBatch(Iterable<ID> ids);

    void deleteAllInBatch();

    /** @deprecated */
    @Deprecated
    T getOne(ID id);

    /** @deprecated */
    @Deprecated
    T getById(ID id);

    T getReferenceById(ID id);

    <S extends T> List<S> findAll(Example<S> example);

    <S extends T> List<S> findAll(Example<S> example, Sort sort);
}

这里的@NoRepositoryBean的作用就是不为此类创建Repository Bean实例

使用Spring Data JPA全自动框架,需要首先创建相关的表对应的实体类,这里的表是自动创建到数据库中,所以需要手动定义相关的主键和相关的约束

@Entity
@Table(name = "ve_user")
@Data
@Accessors(chain = true)
public class veUser {
    private static final long serialVersionUID = 1L;
    
    //主键
    @Id
    @Column(name = "id", nullable = false) //NOTNULL
    private Integer id;
    //姓名
    @Column(name = "user_name",nullable = false)
    private String userName;
    //身高
    @Column(name = "height")
    private BigDecimal height;
    //体重
    @Column(name = "weight")
    private BigDecimal weight;
    //BMI
    @Column(name = "BMI")
    private BigDecimal BMI;
}

使用@Entity就可以标注该类为实体类,框架就会自动更新相关的表【需要进行配置】,@Id标注主键,@Colum标注表中对应的字段名,还可以进行约束,@Table指定对应的表名

配置jpa

spring:
  datasource:
    url: jdbc:mysql://localhost:3306/presis?servertimezone=GMT%2B8
    username: cfeng
    password: a1234567890b
    #当执行schema和data.sql的用户不同时,可以配置相关的username和password
    driver-class-name: com.mysql.cj.jdbc.Driver
    dbcp2: #连接池的相关配置
      initial-size: 10
      min-idle: 10
      max-idle: 30
      max-wait-millis: 3000
      time-between-eviction-runs-millis: 200000 #检查关闭相关连接的时间
      remove-abandoned-timeout: 200000
  jpa: #Spring data jpa的配置,dialect是properties下面的
    show-sql: true
    open-in-view: true
    database: mysql
    hibernate:
      ddl-auto: update
      naming:
        physical-strategy: org.springframework.boot.orm.jpa.hibernate.SpringPhysicalNamingStrategy #SpringPhysical就是遇到下划线转大写

dbcp2为连接池的相关配置,包括最大,最小的链接数量等

操作的数据库的Respository接口

public interface VeUserRepository extends CrudRepository<VeUser,Integer> {
    //按照规范书写相关方法名
    List<VeUser> findByUserName(String userName);

    //查询比height高的用户
    VeUser findByHeightGreaterThan(BigDecimal height);
}

JPA框架会自动创建其相关的实例,所以可以直接使用

@SpringBootTest
public class JpaTests {

    @Resource
    private VeUserRepository veUserRepository;   

@Test
    public void testJpaRepository() {
        List<VeUser> list = veUserRepository.findByUserName("张三");
        assert list != null;
        System.out.println(list);
    }

完成查询只需要一个findBy{:colum}格式的方法,定义之后,框架自动创建相关的实现类实例对象放入容器中等待使用,这里的findBy可以替换为getBy等,因为Spring Data JPA会对方法进行解析,解析的过程为:去掉findBy等关键字,根据剩下的字段名和关键字生成对应的查询的代码【SQL语句】

JPA的相关关键字:

  • And: findByLastNameAndFirstName ----> 对应的就是 where x.lastname = ? and x.firstname = ?
  • Or : xxxxxxxOrFirstName ---- > where x.lastname = ? or x.firstname = ?
  • IsEquals: FindByFirstNameEquals -----> where x.firstname = ?
  • Between: findByStartDateBetween —> where x.startDate between ? and ?
  • LessThan: findByAgeLessThan --> where x.age < ?
  • LessThanEqual: findByAgeLessThanEqual —> where x.age <= ?
  • GreaterThan: findByAgeGreaterThan —> where x.age > ?
  • GreaterThanEqual: findByAgeGreaterThanEqual —> where x.age >= ?

对于日期类型的比较就使用的是After和Before

  • After: findByStartDateAfter —> where x.startDate > ?
  • Before < ?

对于非空null,就是IsNull,IsNotNull

  • IsNull : findByAgeIsNull --> where x.age is null
  • IsNotNull : is not null

模糊查询对应的是StartingWith,EndingWith,Containing 分别为%X X% %X%

  • StartingWith: findByFirstNameStartingWith —> where x.firstName like X%
  • EndingWith : %X
  • Containing: %X%

还有诸如排序的OrderBy,不相等的Not, in 和 not In 对应的是In 和NotIn,还有IgnoreCase忽略大小写比较

  • OrderBy: findByAgeOrderByLastNameDesc ----> where x.age = ? order by x.lastname desc
  • Not: findByLastNameNot ----> where x.lastname <> ?
  • In: findByLastNameIn ----> where x.lastname in ?
  • TRUE/FALSE: findByAgeTrue —> where x.age = true/false
  • Ignorecase: findByFirstNameIgnoreCase: —> where UPPER(x.firstName) = UPPER(?)

基于JpaSpecificationExecutor接口查询

JpaRepository和CrudRepository固然方便,但是对于逻辑复杂的需求,不方便实现,对于难以实现的部分,Spring Data JPA提供了JpaSpecificationExecutor

public interface JpaSpecificationExecutor<T> {
    //根据spec查询一个Optional的实体类【包装】
    Optional<T> findOne(@Nullable Specification<T> spec);
	//根据spec查询一个实体列表
    List<T> findAll(@Nullable Specification<T> spec);
	//更具spec查询一个实体分页
    Page<T> findAll(@Nullable Specification<T> spec, Pageable pageable);
	//查询之后根据sort进行排序
    List<T> findAll(@Nullable Specification<T> spec, Sort sort);
	//查询满足spec条件的实体长度
    long count(@Nullable Specification<T> spec);
	//查询实体是否存在
    boolean exists(Specification<T> spec);
}

Specification提供的toPredicate方法,便于开发人员构造复杂的查询条件

public interface Specification<T> extends Serializable {
    long serialVersionUID = 1L;

    static <T> Specification<T> not(@Nullable Specification<T> spec) {
        return spec == null ? (root, query, builder) -> {
            return null;
        } : (root, query, builder) -> {
            return builder.not(spec.toPredicate(root, query, builder));
        };
    }

    static <T> Specification<T> where(@Nullable Specification<T> spec) {
        return spec == null ? (root, query, builder) -> {
            return null;
        } : spec;
    }

    default Specification<T> and(@Nullable Specification<T> other) {
        return SpecificationComposition.composed(this, other, CriteriaBuilder::and);
    }

    default Specification<T> or(@Nullable Specification<T> other) {
        return SpecificationComposition.composed(this, other, CriteriaBuilder::or);
    }

    @Nullable
    Predicate toPredicate(Root<T> root, CriteriaQuery<?> query, CriteriaBuilder criteriaBuilder);
}

所以在实现项目中的Repository时,往往同时实现JpaRepository和JpaSpecificationExecutor,同时具备各种能力

public interface VeUserRepository extends CrudRepository<VeUser,Integer>, JpaSpecificationExecutor<VeUser> { //接口是可以多继承的

这里可以测试一下: 给出一个时间区间和关键字,查出创建时间在此区间的用户,并且用户包含该关键字

    private List<VeUser> getVeUser(@Nullable LocalDateTime start, @Nullable LocalDateTime end, @Nullable String keyWord) {
        //查询是按照关键字或者时间组合查询,可能没有关键字
        //String.format中%为特殊字符,需要加一个%进行转义  %s%
        String nameLike = keyWord == null ? null : String.format("%%s%%",keyWord);
        //其中可以为Specification,Lambda实现函数接口
        return veUserRepository.findAll(((root, query, criteriaBuilder) -> {
            //根据传入参数的不同构造谓词Predicate列表 criteria 条件
            List<Predicate> predicates = new ArrayList<>();
            //root就是查询的表记录,query就是查询对象,criteriaBuilder就是条件构造器,可以构造between等条件,加入到谓词列表中【比较的条件】
            if(start != null && end != null) {
                predicates.add(criteriaBuilder.between(root.get("createTime"),start,end));
            }
            if(nameLike != null) {
                predicates.add(criteriaBuilder.like(root.get("ve_name"),nameLike));
            }
            //查询条件列表转为数组,放入query.where中进行条件查询
            query.where(predicates.toArray(new Predicate[0]));
            return query.getRestriction(); //得到约束后的查询结果
        }));
    }

    @Test
    public void complicateJpaTest_withExecutor() {
        //首先创建一个user
        VeUser susan = new VeUser();
        susan.setUserName("susan");
        susan.setId(2);
        susan.setWeight(new BigDecimal(50.00));
        susan.setHeight(new BigDecimal(168.00));
        susan.setBMI(new BigDecimal(18.02));
        susan.setCreateTime(LocalDateTime.now());
        System.out.println(veUserRepository.save(susan));
        //根据时间和关键字查询
        List<VeUser> queryWithTime = getVeUser(LocalDateTime.of(2022, 7, 10, 0, 0, 0), LocalDateTime.of(2022, 7, 17, 0, 0, 0), null);
        assert queryWithTime != null;
    }

这样就可以进行查询了,根据不同的字段和变量创建不同的谓词列表Predictes,主要就是toPredicate方法的构造相关的查询条件并进行查询获得getRestirction

基于JPQL和SQL, 依赖注解@Query

上面的方式都没有直接使用SQL语句,和Mybatis相同,JPA也是可以基于SQL的,因为不同的数据库的SQL语法略有差异,提供了另外的Java Persistence Query Language,JPQL进行sql的编写

使用Sql的关键就是@Query注解,将SQL语句给出,代表该方法对应的是该SQL语句【JPA本身就是解析方法为SQL语句】

    //SQL
    //下面这个方法名没有遵循关键字的规范
    @Query(nativeQuery = true,value = "select ve_name from ve_user where (create_time between ? and ?)  and  ve_name like ?")
    List<VeUser> queryByTimeAndName(LocalDateTime start,LocalDateTime end,String keyWord);

//JPQL
@Query("from User user1 where (user1.createTime between ? and ?) and (user1.ve_name like ?)")

JPQL与SQL通过nativeQuery参数进行区分,当nativeQuery为True时,查询语句当作SQL处理,JPQL语法与SQL非常相似,但是JPQL对表和字段的描述使用的实体类及其属性表达,属性名是区分大小写的,所以属性名一定要对应一致

多表连接

Spring Data JPA在处理复杂业务系统时,能够像对待对象一样管理两个表之间的关系,因为表就是@Entity自动更新的,根据业务逻辑,可以创建单向或者双向关系

表与表字段之间的关系可以是一对一,或者多对一,或者多对多,使用@ManyToOne等注解即可表示

@OneToOne 一对一

使用该注解声明表关系时,首先需要关注的是外键的所有者,在外键所有者的实体类中,@OnetoOne注解需要配合==@JoinColum==一起使用,@JoinColum用来声明外键的字段名

@Entity
@Data
@Accessors(chain = true)
@Table(name = "t_class")
public class Class {
    private static final long serialVersionUID = 1L;

    @Id
    @Column(name = "id",nullable = false)
    private Integer id;

    @Column(name = "class_name")
    private String className;

    @OneToMany(fetch = FetchType.LAZY,mappedBy = "clazz") //mappedBy可以让Student实体访问Class实体,因为为双向关系
    private List<Student> students;

    @OneToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "room id")
    private ClassRoom classRoom;
}

@OneToMany 和@ManyToOne

多对一的关系,上面已经进行了演示,对于Class和Student来说,为一对多关联关系; 在Class表中加@OneToMany,在Student表中加@ManyToOne;单向关系,不能使用mappedBy,同时一般使用的是@ManyToOne,同时也要加上@JoinColum表外键的字段

@ManyToMany

多对多的关联关系一般需要中间表的协助,通过@JoinTable来指定中间表

其中name指定表名,joinColums指定正向连接字段名,inverseJoinColum指定反向连接的字段名

//多对多为双向关系,另外一个表可以不用@JoinTable批注,而是用mappedBy属性指定
@ManyToMany
@JoinTable(name = "techer_class", joinColums = {@JoinColum(name = "class_id")}, inverseJoinColums = {@JoinColum(name = "teacher_id")})
private Set<Class> classes;

级联操作cascade

级联操作基于多表连接,在上面的@ManyToOne等注解中,包含一个cascade属性设置表之间的级联操作,描述多个表更新之后所发生的级联反应

级联操作有不同的等级:

  • PERSIST: 级联保存 ,当前实体保存,相关联的实体也保存
  • REMOVE: 级联删除,当前实体删除, 也删除
  • MERGE: 级联合并,当前实体数据更新, 也更新
  • REFRESH: 级联刷新,A,B同时操作一个订单实体及其相关数据,A先于B修改保存,那么B操作的时候,就需要先刷新订单实体,再进行保存
  • DETACH: 级联脱离 实体与其他实体的联系分离
  • ALL: 包含上面的所有级联

加载类型fetchType

加载类型FetchType,和cascade一样是关系注解的配置项,加载类型分为EAGER,和LAZY,LAZY为默认值,EAGER就是关联实体立刻假爱,而LAZY是需要时才会加载,执行SQL语句

也就是当为LAZY只是执行当前表的SQL语句,当需要其他的表的时候才会进行连接操作,但是EAGER就是只要使用到当前表,就会加上复杂的表连接语句

所以fetchType(取类型)一般设置为LAZY

Spring Data MongoDB

除了关系型数据库之外,还有非关系型数据库占了很重要的一部分,MongoDB作为文档型数据库,具有高性能,易部署的特性;

在SpringBoot中,we使用Spring Data MongoDB集成MongoDB数据库

安装MongoDB

这里就不专门介绍,直接到官网下载安装包解压安装即可,注意配置一下环境变量,这样就可以方便使用bin命令了

D:\>cd D:\MongoDB\Server\4.2\bin

D:\MongoDB\Server\4.2\bin>mongod -dbpath D:\MongoDB\Server\4.2\data\db
2022-07-17T18:07:04.297+0800 I  CONTROL  [main] Automatically disabling TLS 1.0, to force-enable TLS 1.0 specify --sslDisabledProtocols 'none'
2022-07-17T18:07:04.672+0800 W  ASIO     [main] No TransportLayer configured during NetworkInterface startup
2022-07-17T18:07:04.675+0800 I  CONTROL  [initandlisten] MongoDB starting : pid=13004 port=27017 dbpath=D:\MongoDB\Server\4.2\data\db 64-bit host=DESKTOP-4A4BD0R
2022-07-17T18:07:04.675+0800 I  CONTROL  [initandlisten] targetMinOS: Windows 7/Windows Server 2008 R2
2022-07-17T18:07:04.675+0800 I  CONTROL  [initandlisten] db version v4.2.6
2022-07-17T18:07:04.675+0800 I  CONTROL  [initandlisten] git version: 20364840b8f1af16917e4c23c1b5f5efd8b352f8
2022-07-17T18:07:04.675+0800 I  CONTROL  [initandlisten] allocator: tcmalloc
2022-07-17T18:07:04.675+0800 I  CONTROL  [initandlisten] modules: none
2022-07-17T18:07:04.675+0800 I  CONTROL  [initandlisten] build environment:

这样就开启了服务,一定要在data下面创建db文件夹

之后就可以新建一个窗口进行相关的mongo操作,直接使用mongo命令即可

C:\Users\OMEY-PC>mongo
MongoDB shell version v4.2.6
connecting to: mongodb://127.0.0.1:27017/?compressors=disabled&gssapiServiceName=mongodb
Implicit session: session { "id" : UUID("ca813d51-d8a3-43df-9420-60abc3e0efa8") }
MongoDB server version: 4.2.6
Welcome to the MongoDB shell.
For interactive help, type "help".
For more comprehensive documentation, see
        http://docs.mongodb.org/
Questions? Try the support group
        http://groups.google.com/group/mongodb-user
Server has startup warnings:
2022-07-17T18:07:04.747+0800 I  CONTROL  [initandli

同时还可以安装可视化客户端mongodb compass,安装之后建立连接即可【server要一直打开】,url就是 mongodb://127.0.0.1:27017,也就是本机的27107端口

引入stater依赖

想要使用Spring Data MongoDB,只需要引入相关的stater即可,其中就有相关的template对象

		
		<dependency>
			<groupId>org.springframework.bootgroupId>
			<artifactId>spring-boot-starter-data-mongodbartifactId>
		dependency>

<!-- 本来这样就可以了,但是其中的driver-async 4.6.1一直无法解析,所以只好排除之后降低版本 

			
		<dependency>
			<groupId>org.springframework.bootgroupId>
			<artifactId>spring-boot-starter-data-mongodbartifactId>






		dependency>












也就是data-mongoDB,之前的jdbc中就是jdbcTemplate,data-jpa就是Spring Data JPA

Spring Data MongoDB提供两种方式访问数据:

  • 基于MongoTemplate : 遵循Spring Boot的标注模板形式,就像RedisTemplate和JdbcTemplate类似,都是在官方的客户端基础上封装的持久化引擎
  • 基于MongoRepository: 按照Spring Data家族通用的设计模式设计的API

先把MongoDB数据库跑起来: C:\Users\xxx-PC>mongod -dbpath D:\MongoDB\Server\4.2\data\db

Spring Data MongoDB可以通过创建配置类集成AbstractMongoClientConfiguration进行配置,或者神功MongoClient或者MongoTemplate的JavaBean实现

继承AbstractMongoClientConfiguration配置

主要是配置数据库名,相关的url和其它的一些配置

  data:
    mongodb:
      host: localhost
      port: 27017
      database: cfengBase

可以直接在yaml中进行配置,或者采用配置类的方式,这里可以通过@Value引入配置动态修改

public class MongoConfig extends AbstractMongoClientConfiguration {

    @Override
    protected String getDatabaseName() {
        return "cfengBase";
    }

    @Override
    public MongoClient mongoClient() {
        ConnectionString connectionString = new ConnectionString("mongodb://localhost:27107/cfengBase");
        MongoClientSettings mongoClientSettings = MongoClientSettings.builder().applyConnectionString(connectionString).build();
        return MongoClients.create(mongoClientSettings);
    }

    @Override
    protected String getMappingBasePackage() {
        return Collections.singleton("indvi.cfeng.persistencedemo");
    }

除了继承AbstractMongoClientConfiguration之外,还可以通过JavaBean的方式

@Configuration
public class MongoConfig  {
    @Bean
    public MongoClient mongo() {
        ConnectionString connectionString = new ConnectionString("mongodb://localhost:27107/cfengBase");
        MongoClientSettings mongoClientSettings = MongoClientSettings.builder().applyConnectionString(connectionString).build();
        return MongoClients.create(mongoClientSettings);
    }

    @Bean
    public MongoTemplate mongoTemplate() throws Exception {
        return new MongoTemplate(mongo(),"cfengBase");
    }
}

使用MongoTemplate访问MongoDB

Spring Data MongoDB的基本文档查询就是依赖的MongoTemplate常见的方法就是增删改查

insert,save,updateFirst,updateMulti,findAndModify,upsert,remove

创建MongoDB的实体类@Document

在类上面加上@Document表明由MongoDB维护,就类似与之前的Spring Data JPA中的实体类注解==@Entity,还有@Indexed和@CompoundIndex为索引和复合索引, 使用@Filed指定在MongoDB数据库中对应的字段名,和之前的JPA的@Colum类似==

@Data
@Document("MongoWare")
public class MongoWare {

    @Id
    private Integer id;

    @Field(value = "mogo_name")
    private String mogoName;

    @Field(value = "mogo_class")
    private String mogoClass;
}

@Transient和之前的JPA中一样,就是该字段作为普通的属性,不录入到数据库中

Mongdb不支持范型,在使用过程中需要注意不要定义范型

insert(T objectToSave, String collectionName) 用于在初始化时插入到数据库的集合中

这个方法用于初始化时将数据写入数据库,可以指定Collection,不指定就是默认类名

save(T objectToSave) 保存实体,插入或者更新

通过id判断实体是否存在与数据库中,存在就为更新操作,不存在就是插入操作

updateFirst(Query query,UpdateDefintion update,Class EntityClass) 更新查询到的第一条记录,返回值为UpdateResult更新后的结果

这里的查询使用的是Query对象,new Query()之后,addCriteria查询条件,Criteria.where等着和之前的JpaSpecializationExecutor是类似的,就是组合谓词列表

而Update对象则用来进行更新,update的set就和之前的mysql类似,会将查询到的对象的某个属性重新set,

EntityClass就是识别MongoDB管理的entity类型

updateFirst方法只是更新查询结果的第一条记录,查询结果可能有很多

updateMulti((Query query,UpdateDefintion update,Class EntityClass)批量更新

和上面的查询不同,会更新多条记录,所有都会更新,和Mysql类似

findAndModify((Query query,UpdateDefintion update,Class EntityClass)更新第一条记录,返回值为更新前记录

和UpdateFirst只是返回值不同

upsert((Query query,UpdateDefintion update,Class EntityClass) 类似与save,但是这是根据query的结果

查找更新创建实体,无则创建,有则更新,save是根据id判断,这里是根据查询条件判断

remove((Query query,Class EntityClass) 将符合查询条件的对象移除数据库

这里也是根据查询条件移除

   @Resource
   private MongoTemplate mongoTemplate;

    /**
     * 使用MongoTemplate访问更加灵活,可选择Collection等,Repository方式更加简单,不管是Mysql还是redis也是一样,Repository直接操作即可,封装比较彻底
     */

    @Test
    public void testMongoTemplate_insert() {
        MongoWare mongoWare = new MongoWare();
        mongoWare.setMogoName("矿泉水");
        mongoWare.setId(4);
        mongoWare.setMogoClass("HC3001");
        //插入数据库
        mongoTemplate.insert(mongoWare,"testdata");
    }

    @Test
    public void testMongoTemplate_save() {
        MongoWare mongoWare = new MongoWare();
        mongoWare.setId(4);
        mongoWare.setMogoName("矿泉水");
        mongoWare.setMogoClass("HC2001");
        //save id有则更新
        MongoWare mongoWare1 = new MongoWare();
        mongoWare1.setId(3);
        mongoWare1.setMogoName("矿泉水");
        mongoWare1.setMogoClass("HC2009");
        mongoTemplate.save(mongoWare1);
        mongoTemplate.save(mongoWare);
        //这里的执行结果在MongoWare这个集合中重新创建该对象,上面的id同的对象在testdata集合中,不同
    }

    @Test
    public void testMongoTemplate_updateFirst() {
        //有两个矿泉水,查询矿泉水
        Query query = new Query();
        query.addCriteria(Criteria.where("mogoName").is("矿泉水"));
        Update update = new Update();
        update.set("mogoName","xiaoBao");
        mongoTemplate.updateFirst(query,update,MongoWare.class);
        //这里的执行结果就是修改了一个对象
    }

    @Test
    public void testMongoTemplate_updateMulti() {
        Query query = new Query();
        query.addCriteria(Criteria.where("mogoName").is("xiaoBao"));
        Update update = new Update();
        update.set("mogoName","xiaoHuan");
        mongoTemplate.updateMulti(query,update,MongoWare.class);
        //两个对象均被修改为xiaoHuan
    }

    @Test
    public void testMongoTemplate_remove() {
        Query query = new Query();
        query.addCriteria(Criteria.where("mogoName").is("矿泉水"));
        mongoTemplate.remove(query,MongoWare.class);
        //成功移除了对象,其他的方法都很简单,就不一一演示
    }

可以看到执行结果符合预期

【开发技术】SpingBoot数据库与持久化技术,JPA,MongoDB,Redis_第3张图片

使用MongoRepository访问MongoDB【和Redis有所区别】

Redis是直接当作和Mysql等一样,直接继承JpaRepository即可,但是MongoDB有海量数据,所以使用更加独特的MongoRepository,MongoRepository和JPA中的Repository很相似,继承了PagingAndSortingRepository接口和QueryByExampleExecutor< T 》除了基础的增删改查之外,还有排序分页,以及Example匹配对象的方式

使用Repository的方式访问,首先就是创建MongoDB管理的实体类

@Data
@Document("MongoWare")
public class MongoWare {

    @Id
    private Integer id;

    @Field(value = "mogo_name")
    private String mogoName;

    @Field(value = "mogo_class")
    private String mogoClass;
}

之后就是创建Repository继承MongoRepository

public interface WareMongoRepository extends MongoRepository<MongoWare,Integer> {
    //这里的Mongo使用Repository和Reids有所不同,Redis是直接使用的JPA其他数据库一样的CRUDRepository,但是Mongo使用的是MongoRepository
}

其中已经提供了很多基础的方法,这里就先不另外增加了,也是要遵守规范定义方法名称,**为了能够让启动类识别该Repository,需要加上注解@EnableMongoRepositories

@SpringBootApplication
@EnableMongoRepositories(basePackages = "indvi.cfeng.persistencedemo.repository")
public class PresisApplication {

saveAll(Iterable s> entities) 保存集合中所有的对象到MongoDB中,默认是类名同名的Collection中

database就是之前config中设置的base,但是集合自己选定,saveAll方法就是mogorepository的,保存给出的所有的实体对象

exists(Example s> example) 判断是否存在与Example匹配的元素

这里的关键就是Example匹配对象,类似于Objects,使用方式: Example.of(构造的对象), 与构造的对象进行匹配,eg: Example.of(new Student().setName(“zs”))

findAll(Sort sort) 查询所有记录,按照规则排序,可以直接Sort.Direction.xx获取

查询所有的记录即可

findAll(Pageable pageable) 查询所有记录分页【海量】

这里的pageable就是PageRequest.of(0,10),类似与之前的PageHelper

接下来我们就可以使用这个repository进行相关的访问,repository默认创建和类同名的集合

    @Resource
    private MongoRepository<MongoWare,Integer> mongoRepository;

    @Test
    public void testMongoRepository_saveAll() {
        List<MongoWare> wares = new ArrayList<>();
        MongoWare ware1 = new MongoWare();
        ware1.setId(1);
        ware1.setMogoName("xiaoHuan");
        ware1.setMogoClass("HC2001");
        MongoWare ware2 = new MongoWare();
        ware2.setId(2);
        ware2.setMogoName("xiaoBao");
        ware2.setMogoClass("HC2002");
        wares.add(ware1);
        wares.add(ware2);
        mongoRepository.saveAll(wares);
    }

    @Test
    public void testMongoRepository_exists() {
        MongoWare Cfeng = new MongoWare();
        Cfeng.setMogoName("Cfeng");
        boolean isCfengExist = mongoRepository.exists(Example.of(Cfeng));
        System.out.println(isCfengExist ? "存在" : "不存在");
    }

    @Test
    public void  testMongoRepository_findAllWithSort() {
        //这里按照mogoName升序排列,xiaoBao在前
        System.out.println(mongoRepository.findAll(Sort.by(Sort.Direction.ASC,"mogoName")));
    }
        //[MongoWare(id=2, mogoName=xiaoBao, mogoClass=HC2002), MongoWare(id=1, mogoName=xiaoHuan, mogoClass=HC2001)]

    @Test
    public void testMongoRepository_findAllwWithPage() {
        //这里使用pageRequest.of只显示第一页1条记录
        System.out.println(mongoRepository.findAll(PageRequest.of(0,1)));
        //Page 1 of 2 containing indvi.cfeng.persistencedemo.entity.MongoWare instances
    }

我们可以查看compass中的结果,正常,【其他的几个测试结果放在注解程序内文档中】

【开发技术】SpingBoot数据库与持久化技术,JPA,MongoDB,Redis_第4张图片

Spring Data Redis

在NoSQL中,MongoDB是适合处理海量的易于扩展的数据,而Redis是更加注重性能,作为内存型键值数据库,大多数开发中,Redis当作缓存使用,用于缓存其他数据库中的热点数据,提高查询性能

SpringBoot中使用Redis是依靠的Spring Data Redis,和MongoDB类似,也提供了Template和repository两种访问的方式

引入依赖

还是引入相关的起步依赖就可以使用相关的template对象

		<dependency>
			<groupId>org.springframework.bootgroupId>
			<artifactId>spring-boot-starter-data-redisartifactId>
		dependency>

连接Redis由两种客户端解决方案: Jedis和莴苣lettuce; Jedis和dos命令类似,Spring Data Redis默认集成Lettuce,如果要使用Jedis作为客户端,需要额外引入Jedis依赖

		
		<dependency>
			<groupId>redis.clientsgroupId>
			<artifactId>jedisartifactId>
			<version>3.3.0version>
		dependency>

Spring Data Redis提供了RedisTemplate和StringRedisTemplate;二者的区别就是

  • StringRedisTemplate: 把k,v当作String处理,使用的是String的序列化,可读性好
  • ReidsTemplate: 把k,v经过序列化存到Redis,可读性不好

默认使用的是JDK序列化,序列化就是将对象转为可传输的字节

反序列化就是将字节序列还原为对象,序列化必须要实现Serlizable接口,定义相关的序列号

序列化的目的是为了对象跨平台和网络传输,网络传输使用的IO为字节传输,要传输对象就必须序列化

在settings中的editor下面的inspections中选择serilizable without UUID...
public class Vehicle implements Serializable {
    private static final long serialVersionUID = 9019832572160148201L;

可以设置key或者value的u序列化方式: redisTemplate.setKeySerializer(new StringRedisSerializer()) SetValue…; redisTemplate.opsForValue().set(k,v);

  redis:
    port: 6379
    host: localhost
    password:
    jedis:
      pool:
        max-active: 10
        max-idle: 8
        min-idle: 1
        max-wait: 1
    connect-timeout: 6000

配置之后就可以使用RedisTempLate访问

@SpringBootTest
public class redisTests {

    @Resource
    private RedisTemplate redisTemplate;

    @Test
    public void testRedis() {
        //字符串类型操作valueOperations
        ValueOperations valueOperations = redisTemplate.opsForValue();

        valueOperations.set("class","HC2001");

        //取出结果
        System.out.println(valueOperations.get("class"));

    }
}

这里就可以成功连接Windows上面的redis【linux版本需要开虚拟机,关闭防火墙】

上面只是简单的配置,并且使用的是Lettuce作为客户端,这里的配置是在yaml中进行配置,也可以采用javaConfig的方式进行配置,可以配置采用Jedis客户端

@Configuration
public class RedisConfig {
    //使用Lettuce作为客户端需要声明LettuceFactory Bean
    @Bean
    public LettuceConnectionFactory redisConnectionFactory() {
        //redis独立配置host和端口standalone
        RedisStandaloneConfiguration redisStandaloneConfiguration = new RedisStandaloneConfiguration("localhost",6379);
        //有密码也需要通过这个对象设置
//        redisStandaloneConfiguration.setPassword("xxxx");
        //设置首先采用的数据库为1号数据库
        redisStandaloneConfiguration.setDatabase(1);
        return new LettuceConnectionFactory(redisStandaloneConfiguration);
    }

    //使用jedis作为客户端声明该Bean
   // @Bean
    //public JedisConnectionFactory jedisConnectionFactory() {
      //  RedisStandaloneConfiguration redisStandaloneConfiguration = new RedisStandaloneConfiguration("localhost",6379);
//        redisStandaloneConfiguration.setPassword("3434");
        //redisStandaloneConfiguration.setDatabase(1);
        //return new JedisConnectionFactory(redisStandaloneConfiguration);
    //}

    //设置RedisTemplate相关属性,注入Bean
    @Bean
    public RedisTemplate<?,?> redisTemplate() {
        RedisTemplate<String,String> template = new RedisTemplate<>();
        //序列化器
        RedisSerializer<String> stringRedisSerializer = new StringRedisSerializer();
        JdkSerializationRedisSerializer jdkSerializationRedisSerializer = new JdkSerializationRedisSerializer();
        //设置template的连接的相关属性等
        //使用jedis作为客户端
        template.setConnectionFactory(jedisConnectionFactory());
        //设置key,hashKey的序列化方式为String,可读性好【这样就类似StringRedisTemplate】
        template.setKeySerializer(stringRedisSerializer);
        template.setHashKeySerializer(stringRedisSerializer);
        //设置hashValue,Value的序列化方式为jdk方式,因为容量更大,jdk序列化传输更好
        template.setValueSerializer(jdkSerializationRedisSerializer);
        template.setHashValueSerializer(jdkSerializationRedisSerializer);
        //设置事务
        template.setEnableTransactionSupport(true);
        //默认的序列化器,如果不设置将...
        template.afterPropertiesSet();
        return template;
    }
}

当然这里we如果要使用Jedis,那么就需要添加Jedis的依赖

java.lang.ClassNotFoundException: redis.clients.jedis.JedisClientConfig报错就是因为没有启用Jedis客户端,启用Jedis,因为使用了Commons-pool,所以需要配置JedisClientConfiguration.JedisPoolingClientConfigurationBuilder创建一个JedisPoolConfig对象,这个对象的属性可以再yaml中配置

		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-data-redis</artifactId>
			<exclusions>
				<exclusion>
					<groupId>io.lettuce</groupId>
					<artifactId>lettuce-core</artifactId>
				</exclusion>
			</exclusions>
		</dependency>

		<!-- 使用jedis作为客户端-->
		<dependency>
			<groupId>redis.clients</groupId>
			<artifactId>jedis</artifactId>
<!--			<version>3.3.0</version>-->
		</dependency>
		//这里必须要exclude,不然下面的配置不能生效,默认还是会采用lettuce

这里就派出了lettuce-core,加入了Jedis的依赖,这样使用的就是Jedis的客户端了

@EnableCaching 配置类上 启用缓存功能 @Cacheable: 赋予缓存功能,标记方法或者类上,代表该类中所有方法支持缓存

缓存之后Spring就会在该方法执行依次之后将返回值缓存到内存中,下次利用相同的参数来执行该方法的时候就不会直接执行,而是直接从缓存中获取结果,键执行hi默认策略和自定义策略,@Cacheable的3个属性:value,key,condition,Cache就类似一个大Map,有很多Cache,方法放在哪个缓存中,需要指定名称 ------> value指定,cacheNames; key是方法返回值对应的key,默认采用方法参数创建

而Cache需要CacheManager的支持,ConcurrentMapCacheManager内部使用的ConcurrentMap实现

接下来演示按照Jedis配置,再yaml中配置,再创建配置类,再配置类中将yaml中的配置项注入,便于当作属性直接修改

@Primary的作用就是标记Bean,当byType注入的时候有多个bean符合时,注入@Primary标记的Bean

对于yaml中单独的一项,使用@Value(“${}”)注入,对于一个prefix下面的,就直接使用ConfigurationProperties注入给一个对象

将yaml中配置的jedis的所有选项注入给属性,并且将jedisPool的配置注入给一个JedisPoolConfig对象

配置使用Jedis为客户端【Pool】

//yaml配置
  redis:
    port: 6379
    host: localhost
    database: 1
    timeout: 1000
    password:
    jedis:
      pool:
        max-active: 10
        max-idle: 8
        min-idle: 1
        max-wait: 1
           
//配置类
@Configuration
@EnableCaching //在配置类中加入该注解,代表启用缓存功能【缓存就是局部性原理】
public class RedisConfig {
    //将yaml中的属性注入
    @Value("${spring.redis.host}")
    private String host;
    @Value("${spring.redis.database}")
    private Integer database;
    @Value("${spring.redis.port}")
    private Integer port;
    @Value("${spring.redis.password}")
    private String password;

    @Primary //标记优先级最高,当byType注入的时候优先
    @Bean(name = "jedisPoolConfig")
    @ConfigurationProperties(prefix = "spring.redis.jedis.pool") //将其中pool下面的属性当作一个对象注入给pool
    public JedisPoolConfig jedisPoolConfig() {
        JedisPoolConfig jedisPoolConfig = new JedisPoolConfig();
        jedisPoolConfig.setMaxWait(Duration.ofSeconds(10));
        return jedisPoolConfig;
    }

    //使用jedis客户端
    @Bean
    //上面的bean拿下来使用
    public RedisConnectionFactory redisConnectionFactory(JedisPoolConfig jedisPoolConfig) {
        RedisStandaloneConfiguration redisStandaloneConfiguration = new RedisStandaloneConfiguration(host,port);
        redisStandaloneConfiguration.setDatabase(database);
//        redisStandaloneConfiguration.setPassword(password);
        //jedis客户端配置,创建poolConfig,这里要加上cast
        JedisClientConfiguration.JedisPoolingClientConfigurationBuilder jedisPoolClientBuilder = (JedisClientConfiguration.JedisPoolingClientConfigurationBuilder) JedisClientConfiguration.builder();
        //使用poolConfig装载pool对象
        jedisPoolClientBuilder.poolConfig(jedisPoolConfig);
        JedisClientConfiguration jedisClientConfiguration = jedisPoolClientBuilder.build();
        //不仅仅配置port等,还要将配置的jedisPool加入
        return new JedisConnectionFactory(redisStandaloneConfiguration,jedisClientConfiguration);
    }

    //需要使用的参数会自动注入容器中的对象,其他的和之前的相同
    @Primary
    @Bean(name = "redisTemplate")
    public RedisTemplate<String,Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {
        RedisTemplate<String,Object> template = new RedisTemplate<>();
        template.setConnectionFactory(redisConnectionFactory);
        //序列化器
        StringRedisSerializer stringRedisSerializer = new StringRedisSerializer();
        template.setKeySerializer(stringRedisSerializer);
        template.setHashKeySerializer(stringRedisSerializer);
        //设置value的序列化器为Json,JDK可读性差
        Jackson2JsonRedisSerializer redisSerializer = new Jackson2JsonRedisSerializer(Object.class);
        template.setValueSerializer(redisSerializer);
        template.setHashValueSerializer(redisSerializer);
        //设置默认
        template.afterPropertiesSet();
        return template;
    }

配置的时Jedis的factory,而@ConfigurationProperties可以直接用在对象上面,不只是放在类上面,可以直接放在@Bean创建的对象上面,再将yaml中配置的注入即可,所以一般都是采用yaml + JavaConfig结合的方式

使用RedisRepository访问Redis

redisRepoitory使用的是Spring Data中通用的Repository的风格

和前面的Jpa和MongoDB类似,Jedis风格的实体类采用@RedisHash,代表实体存储在RedisHash中,使用Repository访问,一定需要这个注解,timeToLive标注存活时间,单位为s,@Indexed和之前的一样代表添加索引

Redis Hash实体类

主要注解和之前的Jpa的@Entity和MongoDB中的@Document类似

/**
 * @author Cfeng
 * @date 2022/7/18
 * Redis是集群部署,实体hash对象需要进行网络传输,需要序列化,序列化的方式一般为jdk或者json
 */

@Data
@Accessors(chain = true)
@RedisHash(value = "Student",timeToLive = 10) //设置存活时间10s
public class RedisStudent  {
    //性别枚举
    public enum Gender {
        MALE,FEMALE
    }

    private String id;
    @Indexed //redis中的
    private String name;
    //性别
    private Gender gender;
    private  int grade;
}

接下来创建一个Repository,使用@Repository就会创建该访问对象到容器

/**
 * @author Cfeng
 * @date 2022/7/18
 * Repository的方式相当于还是使用JPA,只是存储就会自动识别为redis存储
 */

@Repository
public interface StudentRedisRepository extends CrudRepository<RedisStudent,String> {

    //自定义查询方式,使用@Indexed属性进行查询
    RedisStudent findByName(String name);
}

JPA框架可以动态适应不同的数据库,所以这里就可以自动匹配Redis数据库

   @Resource
    private StudentRedisRepository studentRedisRepository;

    @Test
    public void testRedis() {
        //字符串类型操作valueOperations
        ValueOperations valueOperations = redisTemplate.opsForValue();

        valueOperations.set("class","HC2001");

        //取出结果
        System.out.println(valueOperations.get("class"));

    }
    @Test
    public void testRedisSave_withJpaAndRepository() {
        RedisStudent student = new RedisStudent().setId("20220101001").setName("Cfeng").setGender(RedisStudent.Gender.MALE).setGrade(1);
        //根据ID新增记录
        System.out.println(studentRedisRepository.save(student));

    }

    @Test
    public void testRedisSelect_withJpaAndRepository() {
        assert studentRedisRepository.findById("20220101001").isPresent();
        //根据自定义方法查询,符合JPA规范
        assert studentRedisRepository.findByName("Cfeng") != null;
        System.out.println(studentRedisRepository.findByName("Cfeng"));
    }

    @Test
    public void testRedisDelete_withJpaAndRepository() {
        studentRedisRepository.deleteById("20220101001");
        assert studentRedisRepository.findById("20220101001").isPresent();
    }

    @Test
    public void testRedisFindAll_withJpaAndRepository() {
        studentRedisRepository.save(new RedisStudent().setId("20220101002").setGender(RedisStudent.Gender.MALE).setName("huan").setGrade(2));
        studentRedisRepository.save(new RedisStudent().setId("20220101003").setGender(RedisStudent.Gender.FEMALE).setName("bao").setGrade(3));
        List<RedisStudent> redisStudentList = Lists.newArrayList(studentRedisRepository.findAll());
        assert redisStudentList.size() > 0;
        System.out.println(redisStudentList);
    }

这里加入的这个hash对象就会加入到Redis数据库中,所以JPA也是可以连接Redis数据库的,就是Repository的方式,因为Spring Data是不关心底层数据库的,包括MongoDB都是可以使用Repository的方式,只要在实体类加上不同的标记,对象就会被不同的数据库维护

127.0.0.1:6379[1]> keys *

  1. “Student:20220101001:idx”
  2. “Student”
  3. “Student:name:Cfeng”

可以查询windows-redis数据库中已经有hash对象了

使用RedisTepmlate访问Redis

相较于固定的使用JPA的方式访问Redis,使用RedisTemplate的方式会更加灵活,RedisTemplate是基于原生的Redis命令一系列操作方法,基于5种基础的数据结构:String,List,Hash,Set,ZSet,当然因为Lettuce封装之后的操作命令和原生的有了一些区别,可以重新封装Redis的操作方法使之和原生的操作命令相同

@SpringBootTest
public class redisTests {

    @Resource
    private RedisTemplate<String,Object> redisTemplate;

    @Test
    public void testString_withTemplate() {
        //字符串类型操作valueOperations,设置键值对
//        ValueOperations valueOperations = redisTemplate.opsForValue();
        redisTemplate.opsForValue().set("stuName","Cfeng");
        System.out.println(redisTemplate.opsForValue().get("stuName"));
        //设置带有有效时间的set,设置单位TimeUnit为秒
        redisTemplate.opsForValue().set("stuClass","Hc1920",10, TimeUnit.SECONDS);
        //10s后该值为null,keys * 找不到该key
        redisTemplate.opsForValue().get("stuClass");
    }

    @Test
    public void testList_withTemplate() {
        //列表类型的,使用leftPush等方法,列表也是一个key
        redisTemplate.opsForList().leftPush("Kids","yeOne");
        redisTemplate.opsForList().leftPush("Kids","yeTwo");
        redisTemplate.opsForList().leftPush("Kids","yeThree");
        //查询列表长度
        assert redisTemplate.opsForList().size("Kids") == 3;
        System.out.println(redisTemplate.opsForList().range("Kids",1,3));
        //弹出左右的元素
        assert  Objects.equals(redisTemplate.opsForList().leftPop("Kids"),"yeThree");
        assert  Objects.equals(redisTemplate.opsForList().rightPop("Kids"),"yeOne");
    }

    @Test
    public void testHash_withTemplate() {
        //更新hash第一个参数为hash的键,第二个该hash内的键值对
        redisTemplate.opsForHash().put("employee","empName","Chuan");
        redisTemplate.opsForHash().put("employee","emAge",34);
        redisTemplate.opsForHash().put("employee","emHeight",134.00);
//        System.out.println(redisTemplate.opsForHash().get("employee","empName"));
        //get获取,keys获取所有
        Set<Object> employee = redisTemplate.opsForHash().keys("employee");
        //这里要强制转型【先上转型再下转型的】, 不然类型不对应,抛异常
        employee.forEach(key -> System.out.println(key + ":" + redisTemplate.opsForHash().get("employee",(String)key)));
    }

    @Test
    public void     testZset_withTemplate() {
        //新增zset的内容
        redisTemplate.opsForZSet().add("Teacher","miss Zhang",1);
        redisTemplate.opsForZSet().add("Teacher","Mr Li",3);
        redisTemplate.opsForZSet().add("Teacher","Ms Liu",8);
        //获取zset种sir权重
        System.out.println(redisTemplate.opsForZSet().score("Teacher","Mr Li"));
        //根据权重排序zset
        redisTemplate.opsForZSet().range("Teacher",0,-1).forEach(System.out::println);
    }

redisTemplate的方法都是封装后的,后原生的Redis的命令名称不同,封装一下,如果要使用Jedis,那么使用的就是JedisPool,再加入其他的配置就可

封装自定义redisTemplate 【配置Jedis见上】

实际使用redis缓存的场景,需要让java数据类型和redis六种数据结构的命令对应,这里就将redisTemplate封装为全局的CacheTemplate,按照面向接口的思想,首先定义一个IGlodbalCache接口为缓存封装模板的接口

在项目的cache包下面定义接口及其实现类

package indvi.cfeng.persistencedemo.cache;

import org.springframework.data.redis.core.RedisTemplate;

import java.util.List;
import java.util.Map;
import java.util.Set;

/**
 * @author Cfeng
 * @date 2022/7/18
 * 进一步封装redisTemplate,封装的接口规范
 */

public interface IGlobalCache {
    /**
     *首先就是expire key seconds 设置生存时间,返回值为设置是否成功
     */
    boolean expire(String key, long time);
    /**
     * ttl key  返回key剩余的生存时间
     */
    long ttl(String key);
    /**
     * exists key 查看key是否存在,java中还是直接单独判断合适
     */
    boolean exist(String key);
    /**
     * del key  删除key
     * 参数可选多个
     */
    void del(String... key);
    //select database只能通过配置文件修改

    //===========================String类型的相关操作=================

    /**
     * set key value [timeToLive]
     * get key
     * incr/decr key 将key数字值加1
     */
    boolean set(String key,Object value);

    boolean set(String key, Object value, long timeToLive);

    Object get(String key);
    //java中incr加上自定义数值delta
    long incr(String key, long delta);

    long decr(String key, long delta);

    //======================Hash=============
    /**
     * hset key field value
     * hget key field
     * hmset hmget hgetall
     * hdel
     * hexists
     */
    Object hget(String key, String field);

    Map<Object, Object> hmget(String key);

    boolean hset(String key, String field, Object value);

    boolean hset(String key ,String field, Object value, long timeToLive);

    boolean hmset(String key, Map<String,Object> map);

    boolean hmset(String key, Map<String,Object> map, long timeToLive);

    void  hdel(String key, Object... field);

    boolean hexist(String key, String field);

    //hash递增和递减
    double hincr(String key , String field, double delta);

    double hdecr(String key, String field, double delta);

    //=================Set无序集合==================
    /**
     * sadd key member
     * sismember key member
     * scard key
     * srem key member ..
     * spop key count
     * smembers key 获取集合中所有
     */
    Set<Object> smembers(String key);

    boolean sismember(String key,Object member);

    long sadd(String key, Object... members);

    long sadd(String key, long timeToLive, Object... members);

    long scard(String key);

    long srem(String key, Object... members);

    //=============list列表=======================
    /**
     * lpush/pop key value
     * rpush/pop key value
     * lindex key index
     * lrem key
     * llen key
     * lset key index value 设置index位置的value
     * lrange key start stop
     */
    List<Object> lrange(String key,long start, long end);

    long llen(String key); //获取长度

    Object lindex(String key, long index);

    boolean lpush(String key, Object value);

    boolean lpush(String key, Object value, long timeToLive);

    boolean lpushAll(String key, List<Object> value);

    boolean lpushAll(String key, List<Object> value, long timeToLive);

    boolean rpush(String key, Object value);

    boolean rpush(String key, Object value, long timeToLive);

    boolean rpushAll(String key, List<Object> value);

    boolean rpushAll(String key, List<Object> value, long timeToLive);

    boolean lset(String key, long index, Object value);

    long lrem(String key, long count, Object value);
    //移除start和end之间的元素
    void lrangeRem(String key, long start, long end);
    //返回当前的redis对象
    RedisTemplate getRedisTemplate();
}

上面简单定义了String,hash,set,list数据结构的几种简单的操作方法的封装,接下来就是调用RedisTemplate实现这个接口,创建实现类RedisCacheTemplate

package indvi.cfeng.persistencedemo.cache.impl;

import indvi.cfeng.persistencedemo.cache.IGlobalCache;
import lombok.AllArgsConstructor;
import lombok.Getter;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.TimeUnit;

/**
 * @author Cfeng
 * @date 2022/7/19
 * 封装redisTemplate,遵循之前定义的接口规范
 */

@Getter
@AllArgsConstructor
@Component //创建一个单例Bean放入容器
public class RedisCacheTemplate implements IGlobalCache {

    private RedisTemplate<String,Object> redisTemplate;


    @Override
    public boolean expire(String key, long time) {
        //这里就设置过期时间,但是可能发生异常,发生异常就捕获返回false
        try {
            if(time > 0) {
                redisTemplate.expire(key,time, TimeUnit.SECONDS);
            }
            return  true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }

    @Override
    public long ttl(String key) {
        return redisTemplate.getExpire(key,TimeUnit.SECONDS);
    }

    @Override
    public boolean exist(String key) {
        //可变参数为一个args[]数组
        try {
            return redisTemplate.hasKey(key);
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }

    @Override
    public void del(String... key) {
        //可变参数就要判断为多少个,刚好有删除集合的方法delete collection
        if(key != null && key.length > 0) {
            if(key.length == 1) {
                redisTemplate.delete(key[0]);
            } else {
                redisTemplate.delete(String.valueOf(CollectionUtils.arrayToList(key)));
            }
        }
    }

    @Override
    public boolean set(String key, Object value) {
        try {
            redisTemplate.opsForValue().set(key,value);
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }

    @Override
    public boolean set(String key, Object value, long timeToLive) {
        try {
            if(timeToLive > 0) {
                redisTemplate.opsForValue().set(key, value, timeToLive,TimeUnit.SECONDS);
            } else {
                this.set(key, value);
            }
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }

    @Override
    public Object get(String key) {
        return key == null ? null : redisTemplate.opsForValue().get(key);
    }

    @Override
    public long incr(String key, long delta) {
        if(delta < 0) {
            throw new RuntimeException("递增因子必须大于0");
        }
        return redisTemplate.opsForValue().increment(key,delta);
    }

    @Override
    public long decr(String key, long delta) {
        if(delta < 0) {
            throw new RuntimeException("递减因子必须大于0");
        }
        return redisTemplate.opsForValue().decrement(key, delta);
    }

    @Override
    public Object hget(String key, String field) {
        return redisTemplate.opsForHash().get(key,field);
    }

    @Override
    public Map<Object, Object> hmget(String key) {
        return redisTemplate.opsForHash().entries(key);
    }

    @Override
    public boolean hset(String key, String field, Object value) {
        try {
            redisTemplate.opsForHash().put(key,field,value);
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }

    @Override
    public boolean hset(String key, String field, Object value, long timeToLive) {
        try {
            redisTemplate.opsForHash().put(key,field,value);
            if(timeToLive > 0) {
                this.expire(key,timeToLive);
            }
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }

    @Override
    public boolean hmset(String key, Map<String, Object> map) {
        try {
            redisTemplate.opsForHash().putAll(key,map);
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }

    @Override
    public boolean hmset(String key, Map<String, Object> map, long timeToLive) {
        try {
            redisTemplate.opsForHash().putAll(key,map);
            if(timeToLive > 0) {
                this.expire(key,timeToLive);
            }
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }

    @Override
    public void hdel(String key, Object... field) {
        //刚好可选参数
        redisTemplate.opsForHash().delete(key,field);
    }

    @Override
    public boolean hexist(String key, String field) {
        return  redisTemplate.opsForHash().hasKey(key,field);
    }

    @Override
    public double hincr(String key, String field, double delta) {
        return redisTemplate.opsForHash().increment(key,field,delta);
    }

    @Override
    public double hdecr(String key, String field, double delta) {
        //这里直接-delta就是递减
        return redisTemplate.opsForHash().increment(key,field,-delta);
    }

    @Override
    public Set<Object> smembers(String key) {
        try {
            return redisTemplate.opsForSet().members(key);
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }

    @Override
    public boolean sismember(String key, Object member) {
        try {
            return redisTemplate.opsForSet().isMember(key, member);
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }

    @Override
    public long sadd(String key, Object... members) {
        try {
            return redisTemplate.opsForSet().add(key,members);
        } catch (Exception e) {
            e.printStackTrace();
            return 0;
        }
    }

    @Override
    public long sadd(String key, long timeToLive, Object... members) {
        try {
            long count = redisTemplate.opsForSet().add(key,members);
            if(timeToLive > 0) {
                this.expire(key,timeToLive);
            }
            return count;
        } catch (Exception e) {
            e.printStackTrace();
            return 0;
        }
    }

    @Override
    public long scard(String key) {
        try {
            return redisTemplate.opsForSet().size(key);
        } catch (Exception e) {
            e.printStackTrace();
            return 0;
        }
    }

    @Override
    public long srem(String key, Object... members) {
        try {
            return redisTemplate.opsForSet().remove(key,members);
        } catch (Exception e) {
            e.printStackTrace();
            return 0;
        }
    }

    @Override
    public List<Object> lrange(String key, long start, long end) {
        try {
            return redisTemplate.opsForList().range(key, start, end);
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }

    @Override
    public long llen(String key) {
        try {
            return redisTemplate.opsForList().size(key);
        } catch (Exception e) {
            e.printStackTrace();
            return 0;
        }
    }

    @Override
    public Object lindex(String key, long index) {
        try {
            return redisTemplate.opsForList().index(key, index);
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }

    @Override
    public boolean lpush(String key, Object value) {
        try {
            redisTemplate.opsForList().leftPushIfPresent(key, value);
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }

    @Override
    public boolean lpush(String key, Object value, long timeToLive) {
        try {
            redisTemplate.opsForList().leftPushIfPresent(key, value);
            if(timeToLive > 0) {
                this.expire(key,timeToLive);
            }
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }

    @Override
    public boolean lpushAll(String key, List<Object> value) {
        try {
            redisTemplate.opsForList().leftPushAll(key, value);
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }

    @Override
    public boolean lpushAll(String key, List<Object> value, long timeToLive) {
        try {
            redisTemplate.opsForList().leftPushAll(key, value);
            if(timeToLive > 0) {
                this.expire(key,timeToLive);
            }
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }

    @Override
    public boolean rpush(String key, Object value) {
        try {
            redisTemplate.opsForList().rightPush(key, value);
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }

    @Override
    public boolean rpush(String key, Object value, long timeToLive) {
        try {
            redisTemplate.opsForList().rightPush(key, value);
            if(timeToLive > 0) {
                this.expire(key,timeToLive);
            }
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }

    @Override
    public boolean rpushAll(String key, List<Object> value) {
        try {
            redisTemplate.opsForList().rightPushAll(key, value);
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }

    @Override
    public boolean rpushAll(String key, List<Object> value, long timeToLive) {
        try {
            redisTemplate.opsForList().rightPushAll(key, value);
            if(timeToLive > 0) {
                this.expire(key,timeToLive);
            }
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }

    @Override
    public boolean lset(String key, long index, Object value) {
        try {
            redisTemplate.opsForList().set(key, index, value);
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }

    @Override
    public long lrem(String key, long count, Object value) {
        try {
            long rem = redisTemplate.opsForList().remove(key, count, value);
            return rem;
        } catch (Exception e) {
            e.printStackTrace();
            return 0;
        }
    }

    @Override
    public void lrangeRem(String key, long start, long end) {
        try {
            redisTemplate.opsForList().trim(key, start, end);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

接下来测试使用封装的Template来操作Redis

    @Resource
    private IGlobalCache redisCache;


    @Test
    public void testJedis_withCache() {
        redisCache.set("xiaohuan","isPig");
        redisCache.lpushAll("xiaohuanlist", Arrays.asList("hello","redis"));
        List<Object> list = redisCache.lrange("xiaohuanlist",0,-1);
        System.out.println(redisCache.get("xiaohuan"));
    }

查询数据库操作正确,上面创建的自定义封装类是需要创建对象的,不然无法直接使用,其实还是使用RedisTemplate,只是方法名自定义,和redis原生命令一致

接下来会分享Spring Security的内容,就不用其他的安全框架了

你可能感兴趣的:(软件开发技术,数据库,mongodb,redis,springboot)