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


    sSpringBoot

    内容管理


    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>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    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文件
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    现在已经不是在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);
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20

    有了数据库,那么如何利用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);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24

    使用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);
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20

    这里的查询结果为自定义类型,但是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);
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    这里还是使用的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);
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

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

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

    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);
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    这里只是简单将查询结果封装为一个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());
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    执行结果【当出现…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;
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27

    增删改都是使用的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();
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19

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

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

    查看源图像

    Spring Data JPA

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

    在这里插入图片描述

    该框架是遵守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);
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49

    这里的@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;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24

    使用@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就是遇到下划线转大写
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22

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

    操作的数据库的Respository接口

    public interface VeUserRepository extends CrudRepository<VeUser,Integer> {
        //按照规范书写相关方法名
        List<VeUser> findByUserName(String userName);
    
        //查询比height高的用户
        VeUser findByHeightGreaterThan(BigDecimal height);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    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);
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    完成查询只需要一个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);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    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);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28

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

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

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

        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;
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36

    这样就可以进行查询了,根据不同的字段和变量创建不同的谓词列表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 ?)")
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    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;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21

    @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;
    
    • 1
    • 2
    • 3
    • 4

    级联操作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:
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    这样就开启了服务,一定要在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
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    同时还可以安装可视化客户端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>
    
    
    
    
    
    
    
    
    
    
    
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33

    也就是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
    
    • 1
    • 2
    • 3
    • 4
    • 5

    可以直接在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");
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    除了继承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");
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    使用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;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    @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);
            //成功移除了对象,其他的方法都很简单,就不一一演示
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61

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

    在这里插入图片描述

    使用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;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    之后就是创建Repository继承MongoRepository

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

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

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

    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
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40

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

    在这里插入图片描述

    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>
    
    • 1
    • 2
    • 3
    • 4

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

    		
    		<dependency>
    			<groupId>redis.clientsgroupId>
    			<artifactId>jedisartifactId>
    			<version>3.3.0version>
    		dependency>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    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;
    
    • 1
    • 2
    • 3

    可以设置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
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    配置之后就可以使用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"));
    
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    这里就可以成功连接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;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46

    当然这里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
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    这里就派出了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;
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71

    配置的时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;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22

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

    /**
     * @author Cfeng
     * @date 2022/7/18
     * Repository的方式相当于还是使用JPA,只是存储就会自动识别为redis存储
     */
    
    @Repository
    public interface StudentRedisRepository extends CrudRepository<RedisStudent,String> {
    
        //自定义查询方式,使用@Indexed属性进行查询
        RedisStudent findByName(String name);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    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);
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44

    这里加入的这个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);
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56

    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();
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116
    • 117
    • 118
    • 119
    • 120
    • 121
    • 122
    • 123
    • 124
    • 125
    • 126
    • 127
    • 128
    • 129
    • 130
    • 131
    • 132
    • 133
    • 134
    • 135
    • 136
    • 137
    • 138
    • 139
    • 140
    • 141

    上面简单定义了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();
            }
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116
    • 117
    • 118
    • 119
    • 120
    • 121
    • 122
    • 123
    • 124
    • 125
    • 126
    • 127
    • 128
    • 129
    • 130
    • 131
    • 132
    • 133
    • 134
    • 135
    • 136
    • 137
    • 138
    • 139
    • 140
    • 141
    • 142
    • 143
    • 144
    • 145
    • 146
    • 147
    • 148
    • 149
    • 150
    • 151
    • 152
    • 153
    • 154
    • 155
    • 156
    • 157
    • 158
    • 159
    • 160
    • 161
    • 162
    • 163
    • 164
    • 165
    • 166
    • 167
    • 168
    • 169
    • 170
    • 171
    • 172
    • 173
    • 174
    • 175
    • 176
    • 177
    • 178
    • 179
    • 180
    • 181
    • 182
    • 183
    • 184
    • 185
    • 186
    • 187
    • 188
    • 189
    • 190
    • 191
    • 192
    • 193
    • 194
    • 195
    • 196
    • 197
    • 198
    • 199
    • 200
    • 201
    • 202
    • 203
    • 204
    • 205
    • 206
    • 207
    • 208
    • 209
    • 210
    • 211
    • 212
    • 213
    • 214
    • 215
    • 216
    • 217
    • 218
    • 219
    • 220
    • 221
    • 222
    • 223
    • 224
    • 225
    • 226
    • 227
    • 228
    • 229
    • 230
    • 231
    • 232
    • 233
    • 234
    • 235
    • 236
    • 237
    • 238
    • 239
    • 240
    • 241
    • 242
    • 243
    • 244
    • 245
    • 246
    • 247
    • 248
    • 249
    • 250
    • 251
    • 252
    • 253
    • 254
    • 255
    • 256
    • 257
    • 258
    • 259
    • 260
    • 261
    • 262
    • 263
    • 264
    • 265
    • 266
    • 267
    • 268
    • 269
    • 270
    • 271
    • 272
    • 273
    • 274
    • 275
    • 276
    • 277
    • 278
    • 279
    • 280
    • 281
    • 282
    • 283
    • 284
    • 285
    • 286
    • 287
    • 288
    • 289
    • 290
    • 291
    • 292
    • 293
    • 294
    • 295
    • 296
    • 297
    • 298
    • 299
    • 300
    • 301
    • 302
    • 303
    • 304
    • 305
    • 306
    • 307
    • 308
    • 309
    • 310
    • 311
    • 312
    • 313
    • 314
    • 315
    • 316
    • 317
    • 318
    • 319
    • 320
    • 321
    • 322
    • 323
    • 324
    • 325
    • 326
    • 327
    • 328
    • 329
    • 330
    • 331
    • 332
    • 333
    • 334
    • 335
    • 336
    • 337
    • 338
    • 339
    • 340
    • 341
    • 342
    • 343
    • 344
    • 345
    • 346
    • 347
    • 348
    • 349
    • 350
    • 351
    • 352
    • 353
    • 354
    • 355
    • 356
    • 357
    • 358
    • 359
    • 360
    • 361
    • 362
    • 363
    • 364
    • 365
    • 366
    • 367
    • 368
    • 369
    • 370
    • 371
    • 372
    • 373
    • 374
    • 375
    • 376
    • 377
    • 378
    • 379
    • 380
    • 381
    • 382
    • 383
    • 384
    • 385
    • 386
    • 387
    • 388
    • 389
    • 390
    • 391
    • 392
    • 393
    • 394
    • 395
    • 396
    • 397
    • 398
    • 399
    • 400
    • 401
    • 402
    • 403
    • 404
    • 405
    • 406
    • 407
    • 408
    • 409
    • 410
    • 411
    • 412
    • 413
    • 414
    • 415
    • 416
    • 417
    • 418
    • 419
    • 420
    • 421
    • 422
    • 423

    接下来测试使用封装的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"));
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

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

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

  • 相关阅读:
    我们又组织了一次欧洲最大开源社区活动,Hugging Face 博客欢迎社区成员发帖、Hugging Chat 功能更新!
    Linux常用命令
    LeetCode 算法:二叉树的直径 c++
    Linux ARM平台开发系列讲解(PCIE) 2.13.4 从软件的角度去学习PCIE硬件结构
    【TS】Error: Property ‘click‘ does not exist on type ‘Element‘
    NDK交叉编译
    OCP Java17 SE Developers 复习题05
    CPDA|优秀大数据分析师有哪些必备技能?
    Java多线程详解、多线程的创建方式
    【每日一题】打卡 55
  • 原文地址:https://blog.csdn.net/a23452/article/details/125839843