SpringBoot2学习笔记——数据访问

数据访问

JDBC 场景

  1. 导入 JDBC 场景启动器依赖。

    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-data-jdbc</artifactId>
    </dependency>
    
  2. JDBC 场景启动器中没有导入数据库驱动,需要手动导入依赖。

    <dependency>
       <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
    </dependency>
    
  3. 在 application.yaml 中配置数据库连接信息。
    (1)数据源相关的配置:spring.datasource,当我们没有自定义数据库连接池时,SpringBoot 默认使用 HikariDataSource 数据库连接池。
    (2)JDBC 场景启动器中已将 JdbcTemplate 对象保存到 IOC 容器中了,修改 JdbcTemplate 相关的配置:spring.jdbc

    #数据库
    spring:
      datasource:
        driver-class-name: com.mysql.cj.jdbc.Driver
        url: jdbc:mysql://localhost:3306/book?serverTimezone=UTC&rewriteBatchedStatements=true
        username: root
        password: 密码
        #指定使用的数据库连接池
        type: com.zaxxer.hikari.HikariDataSource
      
      #JdbcTemplate
      jdbc:
        template:
          #超时时间
          query-timeout: 5
    
  4. 测试。

    @Slf4j
    @SpringBootTest
    class WebApplicationTests {
        @Autowired
        private JdbcTemplate jdbcTemplate;
        @Test
        void contextLoads() {
            String sql = "select count(*) from book";
            Integer count = jdbcTemplate.queryForObject(sql, Integer.class);
            log.info("总记录数为:{}", count);
        }
    }
    

Druid

使用 Druid 数据库连接池

官方Github地址,中文版Document地址

  1. 当我们没有自定义数据库连接池时,SpringBoot 默认使用 HikariDataSource 数据库连接池;
  2. 当我们向容器中添加了自定义的数据库连接池后,SpringBoot 就会使用我们自定义的数据库连接池。
  3. 当我们自定义了多个数据库连接池时,可以使用spring.datasource.type=指定要使用的数据库连接池。

实现步骤:

  1. 引入 Druid 数据库连接池依赖。

    <properties>
        <druid-version>1.1.23</druid-version>
    </properties>
    
    <!--druid数据库连接池-->
    <dependency>
        <groupId>com.alibaba</groupId>
        <artifactId>druid</artifactId>
        <version>${druid-version}</version>
    </dependency>
    
  2. 在配置类中使用 @Bean 将 druid 对象添加到 IOC 容器中,使用@ConfigurationProperties(prefix = "spring.datasource"),将配置文件中的 url 等属性,与 druid 对象绑定。

    @Configuration
    public class DruidDataSourceConfig {
        @Bean
        @ConfigurationProperties(prefix = "spring.datasource")
        public DataSource dataSource(){
           	DruidDataSource druidDataSource= new DruidDataSource();
        	return druidDataSource;
        }
    }
    
    spring:
      datasource:
        driver-class-name: com.mysql.cj.jdbc.Driver
        url: jdbc:mysql://localhost:3306/book?serverTimezone=UTC&rewriteBatchedStatements=true
        username: root
        password: 密码
        #指定使用的数据库连接池
    #    type: com.alibaba.druid.pool.DruidDataSource
    
  3. 测试。

    @Slf4j
    @SpringBootTest
    class WebApplicationTests {
        @Autowired
        private JdbcTemplate jdbcTemplate;
        @Autowired
        private DataSource dataSource;
        @Test
        void contextLoads() {
            String sql = "select count(*) from book";
            Integer count = jdbcTemplate.queryForObject(sql, Integer.class);
            log.info("总记录数为:{}", count);//总记录数为:25
            log.info("dataSource: {}", dataSource.getClass());//dataSource: class com.alibaba.druid.pool.DruidDataSource
        }
    }
    

开启 Druid 的监控功能

使用 Druid 的内置监控页面

SpringBoot2学习笔记——数据访问

向 IOC 容器中添加:com.alibaba.druid.support.http.StatViewServlet,并设置处理的请求路径为:/druid/*

@Bean
public ServletRegistrationBean<StatViewServlet> statViewServlet(){
    StatViewServlet statViewServlet = new StatViewServlet();
    ServletRegistrationBean<StatViewServlet> servletRegistrationBean = new ServletRegistrationBean<>(statViewServlet, "/druid/*");
    return servletRegistrationBean;
}

为监控页添加用户名和密码:

@Bean
public ServletRegistrationBean<StatViewServlet> statViewServlet(){
    StatViewServlet statViewServlet = new StatViewServlet();
    ServletRegistrationBean<StatViewServlet> servletRegistrationBean = new ServletRegistrationBean<>(statViewServlet, "/druid/*");
    //为监控页添加用户名和密码
    servletRegistrationBean.addInitParameter("loginUsername","admin");
    servletRegistrationBean.addInitParameter("loginPassword","admin");
    return servletRegistrationBean;
}

SpringBoot2学习笔记——数据访问

打开 Druid 的 SQL 监控统计功能

SpringBoot2学习笔记——数据访问

@Bean
@ConfigurationProperties(prefix = "spring.datasource")
public DataSource dataSource() throws SQLException {
    DruidDataSource druidDataSource = new DruidDataSource();
    //打开 Druid 的 SQL 监控统计功能
    druidDataSource.setFilters("stat");
    return druidDataSource;
}

打开 Druid 的 Web、URI、Session 监控功能

SpringBoot2学习笔记——数据访问

@Bean
public FilterRegistrationBean<WebStatFilter> webStatFilter(){
    WebStatFilter webStatFilter = new WebStatFilter();
    FilterRegistrationBean<WebStatFilter> filterRegistrationBean = new FilterRegistrationBean<>(webStatFilter);
    filterRegistrationBean.setUrlPatterns(Arrays.asList("/*"));
    filterRegistrationBean.addInitParameter("exclusions","*.js,*.gif,*.jpg,*.png,*.css,*.ico,/druid/*");
    return filterRegistrationBean;
}

打开 Druid 的 SQL 防火墙功能

SpringBoot2学习笔记——数据访问

@Bean
@ConfigurationProperties(prefix = "spring.datasource")
public DataSource dataSource() throws SQLException {
    DruidDataSource druidDataSource = new DruidDataSource();
    //打开 Druid 的 SQL 监控统计功能
    //打开 Druid 的 SQL 防火墙功能
    druidDataSource.setFilters("stat,wall");
    return druidDataSource;
}

使用场景启动器实现 Druid

druid-spring-boot-starter官方文档地址
SQL防火墙配置项都有哪些

  1. 引入 Druid 场景启动器。
    引入之后,会自动在 application.yaml 中查找spring.datasource信息,创建 DataSource 数据库连接池对象,并放进 IOC 容器中,不需要我们自己编写配置类、创建连接池对象、再放到容器中了。

    <dependency>
       <groupId>com.alibaba</groupId>
       <artifactId>druid-spring-boot-starter</artifactId>
       <version>1.1.17</version>
    </dependency>
    
  2. 可以修改的配置项:
    (1)spring.datasource.druid:Druid 的拓展配置。
    (2)spring.datasource.druid.aop-patterns:配置 Spring 监控。
    (3)spring.datasource.druid.stat-view-servlet:配置监控页。
    (4)spring.datasource.druid.filter:配置 SQL 统计、防火墙等。
    (5)spring.datasource.druid.web-stat-filter:配置 Web应用、URI监控、Session监控。

  3. 在配置文件中进行配置,开启功能。

    spring:
      #数据库
      datasource:
        driver-class-name: com.mysql.cj.jdbc.Driver
        url: jdbc:mysql://localhost:3306/book?serverTimezone=UTC&rewriteBatchedStatements=true
        username: root
        password: 密码
        #指定使用的数据库连接池
        #type: com.alibaba.druid.pool.DruidDataSource
    
        # 配置druid数据源的拓展配置
        druid:
          # 开启监控页
          stat-view-servlet:
            enabled: true
            login-username: admin
            login-password: admin
            url-pattern: /druid/*
            reset-enable: false
    
          # 开启 SQL 统计和防火墙
          filters: stat,wall
    
          # 配置 SQL 统计
          filter:
            stat:
              enabled: true
              # 慢查询毫秒数
              slow-sql-millis: 2000
              # 记录慢查询
              log-slow-sql: true
    
            # 配置 SQL 防火墙
            wall:
              enabled: true
              config:
                # 不允许删表
                drop-table-allow: false
    
    
          # 配置 Web应用、URI监控、Session监控
          web-stat-filter:
            enabled: true
            url-pattern: /*
            exclusions: '*.js,*.gif,*.jpg,*.png,*.css,*.ico,/druid/*'
    
          # 配置 Spring 监控
          aop-patterns: com.mcc.springboot.* # 监控改包下的所有SpringBean
    

整合 MyBatis

引入 MyBatis 场景启动器、配置环境

官方地址

  1. 引入 mybatis-spring-boot-starter。

    <dependency>
        <groupId>org.mybatis.spring.boot</groupId>
        <artifactId>mybatis-spring-boot-starter</artifactId>
        <version>2.1.4</version>
    </dependency>
    
  2. 在 application.yaml 中,使用mybatis配置相关属性。其中,mybatis.config_location:配置核心配置文件位置。
    mybatis.mapper-locations:配置映射文件位置。
    注意:核心配置文件中的相关配置,都被整合到mybatis的相关属性中了,只需要配置这些属性,即可实现核心配置文件的全部功能,因此,在 SpringBoot 整合 Mybatis 时,不需要编写核心配置文件,只需编写映射文件即可。

    # 配置Mybatis
    mybatis:
      # 映射文件位置
      mapper-locations: classpath:/mapper/*.xml
      # 开启驼峰命名
      configuration:
        map-underscore-to-camel-case: true
      # 为JavaBean起别名
      type-aliases-package: com.mcc.springboot.pojo
    
  3. 编写 Mapper 接口,并在接口上标注@Mapper,也可以在配置类上使用@MapperScan指明 Mapper 接口所在的包,两种方式选择一种即可

    package com.mcc.springboot.mapper;
    
    @Mapper
    public interface BookMapper {
    }
    
    @MapperScan(basePackages = "com.mcc.springboot.mapper")
    @SpringBootApplication
    public class WebApplication {
        public static void main(String[] args) {
            SpringApplication.run(WebApplication.class, args);
        }
    }
    

xml 配置文件实现

BookMapper.java

@Mapper
public interface BookMapper {
    /**
     * 根据 id 查询书籍
     * @param id 编号
     * @return Book
     */
    Book getBookById(Integer id);

    /**
     * 插入book
     * @param book book
     * @return 受影响行数
     */
    Integer insertBook(Book book);
}

BookMapper.xml

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.mcc.springboot.mapper.BookMapper">

    <!--Book getBookById(Integer id);-->
    <!--
    插入时不插入自增的主键,使用useGeneratedKeys开启获取自增主键功能,
    使用keyProperty指定主键。开启之后,mybatis会将自增的主键信息保存到
    刚刚的插入对象中并返回。(同一个对象,修改一个另一个也修改,因此原对象中也多了主键内容)
    -->
    <select id="getBookById" resultType="Book">
        select id,bookname,author,price,sales,stock,img_path from book where id = #{id}
    </select>

    <!--Integer insertBook(Book book);-->
    <insert id="insertBook" useGeneratedKeys="true" keyProperty="id">
        insert into book
            (bookname,author,price,sales,stock)
            values
                   (#{bookname},#{author},#{price},#{sales},#{stock})
    </insert>
</mapper>

测试:

@Slf4j
@SpringBootTest
class WebApplicationTests {
  
    @Autowired
    private BookMapper bookMapper;
    
    @Test
    void testGetBookById(){
        //测试 Book getBookById(Integer id);
        Book book = bookMapper.getBookById(2);
        log.info("id为3的book: {}",book);
        //id为3的book: Book(id=2, bookname=数据结构与算法, author=严敏君, 
       //price=78.5, sales=7, stock=12, imgPath=static/img/default.jpg)
    }

    @Test
    void testInsertBook(){
        //测试 Integer insertBook(Book book);
        Book book = new Book();
        book.setBookname("六级写作与翻译");
        book.setAuthor("王江涛");
        book.setPrice(22);
        book.setSales(10000);
        book.setStock(500);
        bookMapper.insertBook(book);
        log.info("添加的book为: {}",book);
        //添加的book为: Book(id=41, bookname=六级写作与翻译, author=王江涛, 
        //price=22.0, sales=10000, stock=500, imgPath=null)
    }
}

注解实现

注解方式整合 MyBatis 时,连映射文件都不需要了。

实现方式:

  1. 在 Mapper 接口的方法上使用@Insert, @Delete, @Update, @Selete,实现增删改查功能。
  2. 使用@Option,实现操作中的一些其他功能。类似的注解还有@ResultMap等等。

BookMapper.java

@Mapper
public interface BookMapper {
    /**
     * 根据 id 查询书籍
     * @param id 编号
     * @return Book
     */
    @Select("select id,bookname,author,price,sales,stock,img_path from book where id = #{id}")
    Book getBookById(Integer id);

    /**
     * 插入book
     * @param book book
     * @return 受影响行数
     */
    @Insert("insert into book\n" +
            "            (bookname,author,price,sales,stock)\n" +
            "            values\n" +
            "                   (#{bookname},#{author},#{price},#{sales},#{stock})")
    @Options(useGeneratedKeys = true,keyProperty = "id")
    Integer insertBook(Book book);
}

组合实现

简单 SQL 语句使用注解实现,复杂 SQL 语句使用映射文件实现。

@Mapper
public interface BookMapper {
    @Select("select id,bookname,author,price,sales,stock,img_path from book where id = #{id}")
    Book getBookById(Integer id);

    Integer insertBook(Book book);
}
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.mcc.springboot.mapper.BookMapper">

    <!--Integer insertBook(Book book);-->
    <insert id="insertBook" useGeneratedKeys="true" keyProperty="id">
        insert into book
            (bookname,author,price,sales,stock)
        values
            (#{bookname},#{author},#{price},#{sales},#{stock})
    </insert>
</mapper>

整合 MyBatis-Plus

原理

  • 在 application.yaml 中使用:mybatis-plus=,对 mybatis-plus 进行配置 。
  • xml 映射文件有默认存放位置,
    mybatis-plus.mapper-locations=classpath*:/mapper/**/*.xml
  • @Mapper标注的接口会被自动扫描,建议在配置类上使用@MapperScan() 批量扫描。

实现

  1. 导入 MyBatis-Plus 场景启动器。
    MP-starter 已经引入了 MyBatis 和 MyBatis-Spring,因此不需要重复引入后两个依赖了。
<dependency>
    <groupId>com.baomidou</groupId>
    <artifactId>mybatis-plus-boot-starter</artifactId>
    <version>3.4.2</version>
</dependency>

SpringBoot2学习笔记——数据访问

  1. 建库建表。
  2. JavaBean 和 Mapper 接口。
@Data
@NoArgsConstructor
@AllArgsConstructor
@ToString
public class User {
    private String id;
    private String username;
    private String password;
    private String email;
}
@Mapper
public interface UserMapper extends BaseMapper<User> {
}
  1. 测试。
@Autowired
private UserMapper userMapper;

@Test
public void testMP(){
    User user = userMapper.selectById("4");
    log.info("user: {}",user.toString());
}

CRUD

  1. Mapper 接口继承 BaseMapper。
@Mapper
public interface UserMapper extends BaseMapper<User> {
}
  1. Service 接口继承 IService,Service 实现类继承 ServiceImpl,并实现对应的 Service 接口。
public interface UserService extends IService<User> {
}
@Service
public class UserServiceImpl extends ServiceImpl<UserMapper, User>  implements UserService {
}
  1. 控制层注入 Service 对象,并进行操作即可。
@Autowired
private UserService userService;
  1. 如果要丰富 Mapper 接口的功能,可以自定义 xml 映射文件,绑定对应的 Mapper 接口即可。
  2. 如果要丰富 Service 层的功能,可以在 Service 接口中自定义方法,在 Service 实现类中注入 Mapper 对象,并实现方法即可。

分页

  • 在配置类内注入分页对象,启动分页插件。
@Configuration
public class MyBatisPlusConfig {
    @Bean
    public MybatisPlusInterceptor mybatisPlusInterceptor() {
        MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
        interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.H2));
        return interceptor;
    }
}
  • thymeleaf 发送带参数的请求。
    ?类型的参数,在路径最后,使用(name=${value}),发送?name=xxx请求。
    rest 风格时,用{name}占位,在路径最后用(name=${}),为同名占位符赋值。
<!-- Will produce 'http://localhost:8080/gtvg/order/details?orderId=3' (plus rewriting) -->
<a href="details.html" 
   th:href="@{http://localhost:8080/gtvg/order/details(orderId=${o.id})}">view</a>

<!-- Will produce '/gtvg/order/details?orderId=3' (plus rewriting) -->
<a href="details.html" th:href="@{/order/details(orderId=${o.id})}">view</a>

<!-- Will produce '/gtvg/order/3/details' (plus rewriting) -->
<a href="details.html" th:href="@{/order/{orderId}/details(orderId=${o.id})}">view</a>

控制层

@GetMapping("/dynamic_table/{pageNum}")
public String dynamicTable(@PathVariable("pageNum") Integer pageNum, Model model){
    Page<User> userPage = userService.page(new Page<User>(pageNum, 3), null);
    model.addAttribute("userPage",userPage);
    return "table/dynamic_table";
}

页面

<tr class="gradeX" th:each="user,status : ${userPage.records}">
    <td th:text="${status.count}"></td>
    <td th:text="${user.id}">Trident</td>
    <td th:text="${user.username}">Trident</td>
    <td th:text="${user.password}">Internet Explorer 4.0</td>
    <td th:text="${user.email}">Trident</td>
</tr>

<!-- 分页 -->
<div class="row-fluid">
    <div class="span6">
        <div class="dataTables_info" id="hidden-table-info_info">
            第 [[${userPage.current}]] 页,共 [[${userPage.pages}]] 页,总计 [[${userPage.total}]] 条记录
        </div>
    </div>
    <div class="span6">
        <div class="dataTables_paginate paging_bootstrap pagination">
            <ul>
                <li class="prev disabled">
                    <a href="#" th:if="${userPage.hasPrevious()}" th:href="@{/dynamic_table/{num}(num=${userPage.current}-1)}">← Previous</a>
                </li>
                <li class="active" th:each="num:${#numbers.sequence(1,userPage.pages)}" th:class="${userPage.current==num?'active':''}">
                    <a href="#" th:href="@{/dynamic_table/{num}(num=${num})}">[[${num}]]</a>
                </li>
                <li class="next disabled">
                    <a href="#" th:if="${userPage.hasNext()}" th:href="@{/dynamic_table/{num}(num=${userPage.current}+1)}">Next → </a>
                </li>
            </ul>
        </div>
    </div>
</div>

分页后的删除

SpringBoot2学习笔记——数据访问
删除时的两种情况:
(1)删除某一页中的数据后,跳转回该页面。
(2)若删除的数据是该页的最后一个数据,要跳转到上一页面。

<tr class="gradeX" th:each="user,status : ${userPage.records}">
    <td th:text="${status.count}"></td>
    <td th:text="${user.id}">Trident</td>
    <td th:text="${user.username}">Trident</td>
    <td th:text="${user.password}">Internet Explorer 4.0</td>
    <td th:text="${user.email}">Trident</td>
    <td>
        <a class="btn btn-danger btn-sm" type="button"
           th:href="@{/deleteUser/{userId}/{pageNum}(userId=${user.id},pageNum=${userPage.current})}">删除</a>
    </td>
</tr>
/**
 * 删除dynamic_table中的数据
 * @return 跳转回删除页面
 */
@GetMapping("/deleteUser/{userId}/{pageNum}")
public String deleteUser(
        @PathVariable("userId") String userId,
        @PathVariable("pageNum") String pageNum,Model model){
    //删除数据
    userService.removeById(userId);
    //检查删除的数据,是否是该页面最后一条数据,如果是最后一条,则要跳转到上一页
    Page<User> userPage = userService.page(new Page<User>(1, 3), null);
    //删除数据后的总页码
    long totalPages = userPage.getPages();
    long currentNum = Long.parseLong(pageNum);
    currentNum = currentNum>totalPages?currentNum-1:currentNum;
    return "redirect:/dynamic_table/"+currentNum;
}
上一篇:# Druid 配置参数及常见报错


下一篇:关于数据库及druid连接池版本,还有相关配置异常。。。