SpringBoot(四)

4.数据层解决方案

4.1SQL

①数据源技术

  • springboot提供了3款内嵌数据源技术

    1. HikariCP
      • 这是springboot官方推荐的数据源技术,作为默认内置数据源使用。
      • 不配置数据源,那就用这个。
    2. Tomcat提供DataSource
      • web技术导入starter后,默认使用内嵌tomcat
      • 如何不使用HikartCP用tomcat提供的默认数据源对象
        • 把HikartCP技术的坐标排除掉
    3. Commons DBCP
      • 既不使用HikartCP也不使用tomcat的DataSource时,默认使用这个。
  • 以前配置druid时使用druid的starter对应的配置

    spring:
      datasource:
        druid:	
       	  url: jdbc:mysql://localhost:3306/ssm_db?serverTimezone=UTC
          driver-class-name: com.mysql.cj.jdbc.Driver
          username: root
          password: root
    
  • 换成是默认的数据源HikariCP后,直接吧druid删掉即可

    spring:
      datasource:
        url: jdbc:mysql://localhost:3306/ssm_db?serverTimezone=UTC
        driver-class-name: com.mysql.cj.jdbc.Driver
        username: root
        password: root
    
  • 对hikari做的配置,但是url地址要单独配置:

spring:
  datasource:
    url: jdbc:mysql://localhost:3306/ssm_db?serverTimezone=UTC
    hikari:
      driver-class-name: com.mysql.cj.jdbc.Driver
      username: root
      password: root
  • 对hikari做进一步的配置,可以继续配置其独立的属性

    spring:
      datasource:
        url: jdbc:mysql://localhost:3306/ssm_db?serverTimezone=UTC
        hikari:
          driver-class-name: com.mysql.cj.jdbc.Driver
          username: root
          password: root
          maximum-pool-size: 50
    
  • 如果不想使用hikari数据源,使用tomcat的数据源或者DBCP配置格式也是一样的。

②持久化技术

springboot给开发者提供了一套现成的数据层技术,叫做JdbcTemplate。

  1. 导入jdbc对应的坐标,记得是starter

    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-jdbc</artifactId>
    </dependency
    
  2. 自动装配JdbcTemplate对象

    @SpringBootTest
    class Springboot15SqlApplicationTests {
        @Test
        void testJdbcTemplate(@Autowired JdbcTemplate jdbcTemplate){
        }
    }
    
  3. 使用JdbcTemplate实现查询操作(非实体类封装数据的查询操作)

    @Test
    void testJdbcTemplate(@Autowired JdbcTemplate jdbcTemplate){
        String sql = "select * from tbl_book";
        List<Map<String, Object>> maps = jdbcTemplate.queryForList(sql);
        System.out.println(maps);
    }
    
  4. 使用JdbcTemplate实现查询操作(实体类封装数据的查询操作)

    @Test
    void testJdbcTemplate(@Autowired JdbcTemplate jdbcTemplate){
    
        String sql = "select * from tbl_book";
        RowMapper<Book> rm = new RowMapper<Book>() {
            @Override
            public Book mapRow(ResultSet rs, int rowNum) throws SQLException {
                Book temp = new Book();
                temp.setId(rs.getInt("id"));
                temp.setName(rs.getString("name"));
                temp.setType(rs.getString("type"));
                temp.setDescription(rs.getString("description"));
                return temp;
            }
        };
        List<Book> list = jdbcTemplate.query(sql, rm);
        System.out.println(list);
    }
    
  5. 使用JdbcTemplate实现增删改操作

    @Test
    void testJdbcTemplateSave(@Autowired JdbcTemplate jdbcTemplate){
        String sql = "insert into tbl_book values(3,'springboot1','springboot2','springboot3')";
        jdbcTemplate.update(sql);
    }
    
    • 如果想对JdbcTemplate对象进行相关配置,可以在yml文件中进行设定,具体如下:
    spring:
      jdbc:
        template:
          query-timeout: -1   # 查询超时时间
          max-rows: 500       # 最大行数
          fetch-size: -1      # 缓存行数
    

③数据库技术

  • springboot提供了3款内置的数据库
    1. H2
    2. HSQL
    3. Derby
  • 以上三款数据库除了可以独立安装之外,还可以像是tomcat服务器一样,采用内嵌的形式运行在spirngboot容器中。
  • 这三个数据库都可以采用内嵌容器的形式运行,在应用程序运行后,如果我们进行测试工作,此时测试的数据无需存储在磁盘上,但是又要测试使用,内嵌数据库就方便了,运行在内存中,该测试测试,该运行运行
  1. 导入H2数据库对应的坐标

    <dependency>
        <groupId>com.h2database</groupId>
        <artifactId>h2</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-data-jpa</artifactId>
    </dependency>
    
  2. 将工程设置为web工程,启动工程时启动H2数据库

    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    
  3. 通过配置开启H2数据库控制台访问程序,也可以使用其他的数据库连接软件操作

    spring:
      h2:
        console:
          enabled: true
          path: /h2
    
    • web端访问路径/h2,访问密码123456,如果访问失败,先配置下列数据源,启动程序运行后再次访问/h2路径就可以正常访问了
    datasource:
      url: jdbc:h2:~/test
      hikari:
        driver-class-name: org.h2.Driver
        username: sa
        password: 123456
    

4.2NoSQL

NoSQL(非关系型数据库),意思就是数据该存存该取取,只是这些数据不放在关系型数据库中,放在一些能够存储数据的其他相关技术中,比如Redis.

①SpringBoot整合Redis

  • 整合的思想:所谓整合其实就是使用springboot技术去管理其他技术,整合有以下几个问题
    1. 需要先导入对应技术的坐标,而整合之后,这些坐标都有了一些变化
    2. 任何技术通常都会有一些相关的设置信息,整合之后,这些信息如何写,写在哪是一个问题
    3. 没有整合之前操作如果是模式A的话,整合之后如果没有给开发者带来一些便捷操作,那整合将毫无意义,所以整合后操作肯定要简化一些,那对应的操作方式自然也有所不同
  • 按照上面的三个问题去思考springboot整合所有技术是一种通用思想,在整合的过程中会逐步摸索出整合的套路,而且适用性非常强,经过若干种技术的整合后基本上可以总结出一套固定思维。
  • 下面的Redis,MongoDB,es都是如此
  1. 导入springboot整合redis的starter坐标

    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-data-redis</artifactId>
    </dependency>
    <!-- 也可以在创建模块时勾选相对于的选项
    	选择NoSQL,选择Redis -->
    
  2. 进行基础配置

    # 以下配置都是默认的
    spring:
      redis:
        host: localhost
        port: 6379
    # 操作redis,最基本的信息就是操作哪一台redis服务器,所以服务器地址属于基础配置信息,不可缺少。
    
  3. 使用springboot整合redis的专用客户端接口操作,此处使用的是RedisTemplate

    @SpringBootTest
    class Springboot16RedisApplicationTests {
        @Autowired
        private RedisTemplate redisTemplate;
        @Test
        void set() {
            ValueOperations ops = redisTemplate.opsForValue();
            ops.set("age",41);
        }
        @Test
        void get() {
            ValueOperations ops = redisTemplate.opsForValue();
            Object age = ops.get("name");
            System.out.println(age);
        }
        @Test
        void hset() {
            HashOperations ops = redisTemplate.opsForHash();
            ops.put("info","b","bb");
        }
        @Test
        void hget() {
            HashOperations ops = redisTemplate.opsForHash();
            Object val = ops.get("info", "b");
            System.out.println(val);
        }
    }
    

    在操作redis时,需要先确认操作何种数据,根据数据种类得到操作接口。例如使用opsForValue()获取string类型的数据操作接口,使用opsForHash()获取hash类型的数据操作接口,剩下的就是调用对应api操作了。

    image-20220224103104908
  4. StringRedisTemplate

    • 由于redis内部不提供java对象的存储格式,因此当操作的数据以对象的形式存在时,会进行转码,转换成字符串格式后进行操作。

    • 为了方便开发者使用基于字符串为数据的操作,springboot整合redis时提供了专用的API接口StringRedisTemplate,可以理解为这是RedisTemplate的一种指定数据泛型的操作API。

      @SpringBootTest
      public class StringRedisTemplateTest {
          @Autowired
          private StringRedisTemplate stringRedisTemplate;
          @Test
          void get(){
              ValueOperations<String, String> ops = stringRedisTemplate.opsForValue();
              String name = ops.get("name");
              System.out.println(name);
          }
      }
      
Redis客户端选择

springboot整合redis技术提供了多种客户端兼容模式,默认提供的是lettucs客户端技术,也可以根据需要切换成指定客户端技术,例如jedis客户端技术,切换成jedis客户端技术

  1. 导入jedis坐标

    <dependency>
        <groupId>redis.clients</groupId>
        <artifactId>jedis</artifactId>
    </dependency>
    <!-- jedis坐标受springboot管理,无需提供版本号 -->
    
  2. 配置客户端技术类型,设置为jedis

    spring:
      redis:
        host: localhost
        port: 6379
        client-type: jedis
    
  3. 根据需要设置对应的配置

    spring:
      redis:
        host: localhost
        port: 6379
        client-type: jedis
        lettuce:
          pool:
            max-active: 16
        jedis:
          pool:
            max-active: 16
    
  4. lettcus与jedis区别

    • jedis连接Redis服务器是直连模式,当多线程模式下使用jedis会存在线程安全问题,解决方案可以通过配置连接池使每个连接专用,这样整体性能就大受影响
    • lettcus基于Netty框架进行与Redis服务器连接,底层设计中采用StatefulRedisConnection。 StatefulRedisConnection自身是线程安全的,可以保障并发访问安全问题,所以一个连接可以被多线程复用。当然lettcus也支持多连接实例一起工作

②SpringBoot整合MongoDB

  1. 导入springboot整合MongoDB的starter坐标

    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-data-mongodb</artifactId>
    </dependency>
    <!-- 也可以在创建模块时勾选相对于的选项
    	选择NoSQL,选择MongoDB -->
    
  2. 进行基础配置

    spring:
      data:
        mongodb:
          uri: mongodb://localhost/chenchen
    # 操作MongoDB需要的配置与操作redis一样,最基本的信息都是操作哪一台服务器,区别就是连接的服务器IP地址和端口不同,书写格式不同而已。
    
  3. 使用springboot整合MongoDB的专用客户端接口MongoTemplate来进行操作

    @SpringBootTest
    class Springboot17MongodbApplicationTests {
        @Autowired
        private MongoTemplate mongoTemplate;
        @Test
        void contextLoads() {
            Book book = new Book();
            book.setId(2);
            book.setName("springboot2");
            book.setType("springboot2");
            book.setDescription("springboot2");
            mongoTemplate.save(book);
        }
        @Test
        void find(){
            List<Book> all = mongoTemplate.findAll(Book.class);
            System.out.println(all);
        }
    }
    

③SpringBoot整合ES

  1. 导入springboot整合ES的starter坐标

    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-data-elasticsearch</artifactId>
    </dependency>
    
  2. 进行基础配置

    spring:
      elasticsearch:
        rest:
          uris: http://localhost:9200
    # 配置ES服务器地址,端口9200
    
  3. 使用springboot整合ES的专用客户端接口ElasticsearchRestTemplate来进行操作

    @SpringBootTest
    class Springboot18EsApplicationTests {
        @Autowired
        private ElasticsearchRestTemplate template;
    }
    

述操作形式是ES早期的操作方式,使用的客户端被称为Low Level Client,这种客户端操作方式性能方面略显不足,于是ES开发了全新的客户端操作方式,称为High Level Client。高级别客户端与ES版本同步更新,但是springboot最初整合ES的时候使用的是低级别客户端,所以企业开发需要更换成高级别的客户端模式。

下面使用高级别客户端方式进行springboot整合ES

  1. 导入springboot整合ES高级别客户端的坐标,此种形式目前没有对应的starter

    <dependency>
        <groupId>org.elasticsearch.client</groupId>
        <artifactId>elasticsearch-rest-high-level-client</artifactId>
    </dependency>
    
  2. 使用编程的形式设置连接的ES服务器,并获取客户端对象

    @SpringBootTest
    class Springboot18EsApplicationTests {
        private RestHighLevelClient client;
          @Test
          void testCreateClient() throws IOException {
              HttpHost host = HttpHost.create("http://localhost:9200");
              RestClientBuilder builder = RestClient.builder(host);
              client = new RestHighLevelClient(builder);
      
              client.close();
          }
    }
    //配置ES服务器地址与端口9200,记得客户端使用完毕需要手工关闭。由于当前客户端是手工维护的,因此不能通过自动装配的形式加载对象。
    
  3. 使用客户端对象操作ES,例如创建索引

    @SpringBootTest
    class Springboot18EsApplicationTests {
        private RestHighLevelClient client;
          @Test
          void testCreateIndex() throws IOException {
              HttpHost host = HttpHost.create("http://localhost:9200");
              RestClientBuilder builder = RestClient.builder(host);
              client = new RestHighLevelClient(builder);
              
              CreateIndexRequest request = new CreateIndexRequest("books");
              client.indices().create(request, RequestOptions.DEFAULT); 
              
              client.close();
          }
    }
    

    高级别客户端操作是通过发送请求的方式完成所有操作的,ES针对各种不同的操作,设定了各式各样的请求对象,上例中创建索引的对象是CreateIndexRequest,其他操作也会有自己专用的Request对象。

  4. 当前操作我们发现,无论进行ES何种操作,第一步永远是获取RestHighLevelClient对象,最后一步永远是关闭该对象的连接。

    • 在测试中可以使用测试类的特性去帮助开发者一次性的完成上述操作,但是在业务书写时,还需要自行管理。将上述代码格式转换成使用测试类的初始化方法和销毁方法进行客户端对象的维护。

      @SpringBootTest
      class Springboot18EsApplicationTests {
          @BeforeEach		//在测试类中每个操作运行前运行的方法
          void setUp() {
              HttpHost host = HttpHost.create("http://localhost:9200");
              RestClientBuilder builder = RestClient.builder(host);
              client = new RestHighLevelClient(builder);
          }
      
          @AfterEach		//在测试类中每个操作运行后运行的方法
          void tearDown() throws IOException {
              client.close();
          }
      
          private RestHighLevelClient client;
      
          @Test
          void testCreateIndex() throws IOException {
              CreateIndexRequest request = new CreateIndexRequest("books");
              client.indices().create(request, RequestOptions.DEFAULT);
          }
      }
      

下面使用上述模式将所有的ES操作执行一遍,测试结果

  1. 创建索引(IK分词器)

    @Test
    void testCreateIndexByIK() throws IOException {
        CreateIndexRequest request = new CreateIndexRequest("books");
        String json = "{\n" +
                "    \"mappings\":{\n" +
                "        \"properties\":{\n" +
                "            \"id\":{\n" +
                "                \"type\":\"keyword\"\n" +
                "            },\n" +
                "            \"name\":{\n" +
                "                \"type\":\"text\",\n" +
                "                \"analyzer\":\"ik_max_word\",\n" +
                "                \"copy_to\":\"all\"\n" +
                "            },\n" +
                "            \"type\":{\n" +
                "                \"type\":\"keyword\"\n" +
                "            },\n" +
                "            \"description\":{\n" +
                "                \"type\":\"text\",\n" +
                "                \"analyzer\":\"ik_max_word\",\n" +
                "                \"copy_to\":\"all\"\n" +
                "            },\n" +
                "            \"all\":{\n" +
                "                \"type\":\"text\",\n" +
                "                \"analyzer\":\"ik_max_word\"\n" +
                "            }\n" +
                "        }\n" +
                "    }\n" +
                "}";
        //设置请求中的参数
        request.source(json, XContentType.JSON);
        client.indices().create(request, RequestOptions.DEFAULT);
    }
    

    IK分词器是通过请求参数的形式进行设置的,设置请求参数使用request对象中的source方法进行设置,至于参数是什么,取决于你的操作种类。当请求中需要参数时,均可使用当前形式进行参数设置。

  2. 添加文档

    @Test
    //添加文档
    void testCreateDoc() throws IOException {
        Book book = bookDao.selectById(1);
        IndexRequest request = new IndexRequest("books").id(book.getId().toString());
        String json = JSON.toJSONString(book);
        request.source(json,XContentType.JSON);
        client.index(request,RequestOptions.DEFAULT);
    }
    

    添加文档使用的请求对象是IndexRequest,与创建索引使用的请求对象不同。

  3. 批量添加文档

    @Test
    //批量添加文档
    void testCreateDocAll() throws IOException {
        List<Book> bookList = bookDao.selectList(null);
        BulkRequest bulk = new BulkRequest();
        for (Book book : bookList) {
            IndexRequest request = new IndexRequest("books").id(book.getId().toString());
            String json = JSON.toJSONString(book);
            request.source(json,XContentType.JSON);
            bulk.add(request);
        }
        client.bulk(bulk,RequestOptions.DEFAULT);
    }
    

    批量做时,先创建一个BulkRequest的对象,可以将该对象理解为是一个保存request对象的容器,将所有的请求都初始化好后,添加到BulkRequest对象中,再使用BulkRequest对象的bulk方法,一次性执行完毕。

  4. 按id查询文档

    @Test
    //按id查询
    void testGet() throws IOException {
        GetRequest request = new GetRequest("books","1");
        GetResponse response = client.get(request, RequestOptions.DEFAULT);
        String json = response.getSourceAsString();
        System.out.println(json);
    }
    

    根据id查询文档使用的请求对象是GetRequest。

  5. 按条件查询文档

    @Test
    //按条件查询
    void testSearch() throws IOException {
        SearchRequest request = new SearchRequest("books");
    
        SearchSourceBuilder builder = new SearchSourceBuilder();
        builder.query(QueryBuilders.termQuery("all","spring"));
        request.source(builder);
    
        SearchResponse response = client.search(request, RequestOptions.DEFAULT);
        SearchHits hits = response.getHits();
        for (SearchHit hit : hits) {
            String source = hit.getSourceAsString();
            //System.out.println(source);
            Book book = JSON.parseObject(source, Book.class);
            System.out.println(book);
        }
    }
    

    按条件查询文档使用的请求对象是SearchRequest,查询时调用SearchRequest对象的termQuery方法,需要给出查询属性名,此处支持使用合并字段,也就是前面定义索引属性时添加的all属性。

    与前期进行springboot整合redis和mongodb的差别还是蛮大的,主要原始就是我们没有使用springboot整合ES的客户端对象。至于操作,由于ES操作种类过多,所以显得操作略微有点复杂。

posted @   22-10-21  阅读(27)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 阿里巴巴 QwQ-32B真的超越了 DeepSeek R-1吗?
· 10年+ .NET Coder 心语 ── 封装的思维:从隐藏、稳定开始理解其本质意义
· 【设计模式】告别冗长if-else语句:使用策略模式优化代码结构
· 字符编码:从基础到乱码解决
· 提示词工程——AI应用必不可少的技术
点击右上角即可分享
微信分享提示