zoukankan      html  css  js  c++  java
  • 12、SpringBoot与数据访问

    SpringData概述

    对于数据访问层,无论是 SQL(关系型数据库) 还是 NOSQL(非关系型数据库),Spring Boot 底层都是采用 Spring Data 的方式进行统一处理。

    Spring Boot 底层都是采用 Spring Data 的方式进行统一处理各种数据库,Spring Data 也是 Spring 中与 Spring Boot、Spring Cloud 等齐名的知名项目。

    Sping Data 官网:https://spring.io/projects/spring-data

    数据库相关的启动器 :可以参考官方文档:

    https://docs.spring.io/spring-boot/docs/2.2.5.RELEASE/reference/htmlsingle/#using-boot-starter

    JDBC

    pom文件中与数据库有关的启动器

    <!--jdbc-->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-jdbc</artifactId>
    </dependency>
    <!--mysql-->
    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
        <scope>runtime</scope>
    </dependency>
    

    编写yaml配置连接数据库

    spring:
      datasource:
        username: root
        password: root123
        url: jdbc:mysql://localhost:3306/eesy_mybatis?useUnicode=true&characterEncoding=utf-8
        # 5.x版本 com.mysql.jdbc.Driver
        driver-class-name: com.mysql.jdbc.Driver
        # 8.x版本 com.mysql.cj.jdbc.Driver
        # 需要在url添加时区设置:serverTimezone=GMT%2B
    

    测试连接

    package com.coydone;
    
    @SpringBootTest
    class SpringBootDataJdbcApplicationTests {
        @Autowired
        DataSource dataSource;
        @Test
        void contextLoads() throws SQLException {
            //查看一下默认数据源:com.zaxxer.hikari.HikariDataSource
            System.out.println(dataSource.getClass());
    
            //获取数据库连接
            Connection connection = dataSource.getConnection();
            System.out.println(connection);
            connection.close();
        }
    }
    

    注意:

    java.sql.SQLException: The server time zone value:时区异常,springboot现在默认mysql版本为8.x,如果使用5.x会出现此异常。

    url: jdbc:mysql://localhost:3306/eesy_mybatis?serverTimezone=GMT%2B8&useUnicode=true&characterEncoding=utf-8
    #GMT%2B 设置时区为北京时间东八区
    

    数据源的相关配置都在DataSourceProperties类里面;默认配置在DataSourceAutoConfiguration里。

    自动配置原理:

    org.springframework.boot.autoconfigure.jdbc:

    1、参考DataSourceConfiguration,根据配置创建数据源,默认使用hikari连接池;可以使用spring.datasource.type指定自定义的数据源类型;

    2、SpringBoot默认可以支持:

    org.apache.tomcat.jdbc.pool.DataSource、HikariDataSource、BasicDataSource
    

    HikariDataSource 号称 Java WEB 当前速度最快的数据源,相比于传统的 C3P0 、DBCP、Tomcat jdbc 等连接池更加优秀。

    可以使用 spring.datasource.type 指定自定义的数据源类型,值为要使用的连接池实现的完全限定名。

    我们来全局搜索一下,找到数据源的所有自动配置都在 :DataSourceAutoConfiguration。

    @Import({Hikari.class, Tomcat.class, Dbcp2.class, Generic.class, DataSourceJmxConfiguration.class})
    protected static class PooledDataSourceConfiguration {
        protected PooledDataSourceConfiguration() {
        }
    }
    

    这里导入的类都在 DataSourceConfiguration 配置类下,可以看出 Spring Boot 2.2.7 默认使用HikariDataSource 数据源,而以前版本,如 Spring Boot 1.5 默认使用 org.apache.tomcat.jdbc.pool.DataSource 作为数据源;

    3、自定义数据源类型

    @ConditionalOnProperty(
        prefix = "spring.datasource",
        name = {"type"}
    )
    //我们只需要使用spring.datasource.type来自定义
    

    4、DataSourceInitializer

    在package org.springframework.boot.autoconfigure.jdbc下有DataSourceInitializer类

    boolean createSchema() {//建表
        List<Resource> scripts = this.getScripts("spring.datasource.schema", this.properties.getSchema(), "schema");
        if (!scripts.isEmpty()) {
            if (!this.isEnabled()) {
                logger.debug("Initialization disabled (not running DDL scripts)");
                return false;
            }
            String username = this.properties.getSchemaUsername();
            String password = this.properties.getSchemaPassword();
            this.runScripts(scripts, username, password);
        }
        return !scripts.isEmpty();
    }
    private List<Resource> getScripts(String propertyName, List<String> resources, String fallback) {
            if (resources != null) {
                return this.getResources(propertyName, resources, true);
            } else {
                String platform = this.properties.getPlatform();
                List<String> fallbackResources = new ArrayList();
                //获取配置中的建表规则
                fallbackResources.add("classpath*:" + fallback + "-" + platform + ".sql");
                fallbackResources.add("classpath*:" + fallback + ".sql");
                return this.getResources(propertyName, fallbackResources, false);
            }
        }
    

    默认只需要将文件命名为:

    schema-*.sql、data-*.sql
    默认规则:schema.sql,schema-all.sql;
    可以使用   schema: classpath:xxx.sql  自定义sql文件名
    spring.datasource.initialization-mode=always
    spring.datasource.schema=classpath:student.sql,classpath:grade.sql
    

    5、操作数据库:自动配置了JdbcTemplate操作数据库

    @SpringBootTest
    public class JDBCTest {
        @Autowired
        JdbcTemplate jdbcTemplate;
        @Test
        public void testJDBC(){
            String sql = "select * from student";
            List<Map<String, Object>> maps = jdbcTemplate.queryForList(sql);
            System.out.println(maps);
        }
    }
    

    JDBCTemplate

    1、有了数据源(com.zaxxer.hikari.HikariDataSource),然后可以拿到数据库连接(java.sql.Connection),有了连接,就可以使用原生的 JDBC 语句来操作数据库;

    2、即使不使用第三方第数据库操作框架,如 MyBatis等,Spring 本身也对原生的JDBC 做了轻量级的封装,即JdbcTemplate。

    3、数据库操作的所有 CRUD 方法都在 JdbcTemplate 中。

    4、Spring Boot 不仅提供了默认的数据源,同时默认已经配置好了 JdbcTemplate 放在了容器中,程序员只需自己注入即可使用。

    5、JdbcTemplate 的自动配置是依赖 org.springframework.boot.autoconfigure.jdbc 包下的 JdbcTemplateConfiguration 类。

    JdbcTemplate主要提供以下几类方法:

    • execute方法:可以用于执行任何SQL语句,一般用于执行DDL语句。
    • update方法及batchUpdate方法:update方法用于执行新增、修改、删除等语句;batchUpdate方法用于执行批处理相关语句。
    • query方法及queryForXXX方法:用于执行查询相关语句。
    • call方法:用于执行存储过程、函数相关语句。

    整合Druid数据源

    Druid简介

    Java程序很大一部分要操作数据库,为了提高性能操作数据库的时候,又不得不使用数据库连接池。

    Druid 是阿里巴巴开源平台上一个数据库连接池实现,结合了 C3P0、DBCP 等 DB 池的优点,同时加入了日志监控。

    Druid 可以很好的监控 DB 池连接和 SQL 的执行情况,天生就是针对监控而生的 DB 连接池。

    Druid已经在阿里巴巴部署了超过600个应用,经过一年多生产环境大规模部署的严苛考验。

    Spring Boot 2.0 以上默认使用 Hikari 数据源,可以说 Hikari 与 Driud 都是当前 Java Web 上最优秀的数据源,我们来重点介绍 Spring Boot 如何集成 Druid 数据源,如何实现数据库监控。

    Github地址:https://github.com/alibaba/druid/

    在Maven仓库搜索Druid:https://mvnrepository.com/artifact/com.alibaba/druid

    自定义配置类

    选择使用最多的版本

    <!--指定druid数据源-->
    <dependency>
        <groupId>com.alibaba</groupId>
        <artifactId>druid</artifactId>
        <version>1.1.10</version>
    </dependency>
    

    切换数据源

    spring:
      datasource:
        type: com.alibaba.druid.pool.DruidDataSource
    

    配置druid

    spring:
      datasource:
    	#Spring Boot 默认是不注入这些属性值的,需要自己绑定
        #druid 数据源专有配置
        initialSize: 5
        minIdle: 5
        maxActive: 20
        maxWait: 60000
        timeBetweenEvictionRunsMillis: 60000
        minEvictableIdleTimeMillis: 300000
        validationQuery: SELECT 1 FROM DUAL
        testWhileIdle: true
        testOnBorrow: false
        testOnReturn: false
        poolPreparedStatements: true
    
        #配置监控统计拦截的filters,stat:监控统计、log4j:日志记录、wall:防御sql注入
        #如果允许时报错  java.lang.ClassNotFoundException: org.apache.log4j.Priority
        #则导入 log4j 依赖即可,Maven 地址:https://mvnrepository.com/artifact/log4j/log4j
        filters: stat,wall,log4j
        maxPoolPreparedStatementPerConnectionSize: 20
        useGlobalDataSourceStat: true
        connectionProperties: druid.stat.mergeSql=true;druid.stat.slowSqlMillis=500
    

    导入Log4j 的依赖

    <dependency>
        <groupId>log4j</groupId>
        <artifactId>log4j</artifactId>
        <version>1.2.17</version>
    </dependency>
    

    现在需要程序员自己为 DruidDataSource 绑定全局配置文件中的参数,再添加到容器中,而不再使用 Spring Boot 的自动生成了,我们需要自己添加 DruidDataSource 组件到容器中,并绑定属性。

    //导入druid数据源
    @Configuration
    public class DruidConfig {
        /*
        将自定义的 Druid数据源添加到容器中,不再让 Spring Boot 自动创建
        绑定全局配置文件中的 druid 数据源属性到 com.alibaba.druid.pool.DruidDataSource从而让它们生效
        @ConfigurationProperties(prefix = "spring.datasource"):作用就是将全局配置文件中
        前缀为 spring.datasource的属性值注入到 com.alibaba.druid.pool.DruidDataSource 的同名参数中
       */
        @ConfigurationProperties(prefix = "spring.datasource")
        @Bean
        public DataSource druid(){
            return  new DruidDataSource();
        }
    
        //配置Druid的监控
        //1、配置一个管理后台的Servlet
        @Bean
        public ServletRegistrationBean statViewServlet(){
            ServletRegistrationBean bean = new ServletRegistrationBean(new StatViewServlet(), "/druid/*");
            // 这些参数可以在 com.alibaba.druid.support.http.StatViewServlet 
            // 的父类 com.alibaba.druid.support.http.ResourceServlet 中找到
            Map<String, String> initParams = new HashMap<>();
            initParams.put("loginUsername", "admin"); //后台管理界面的登录账号
            initParams.put("loginPassword", "123456"); //后台管理界面的登录密码
    
            //后台允许谁可以访问
            //initParams.put("allow", "localhost"):表示只有本机可以访问
            //initParams.put("allow", ""):为空或者为null时,表示允许所有访问
            initParams.put("allow", "");
            //deny:Druid 后台拒绝谁访问
            //initParams.put("user", "192.168.1.20");表示禁止此ip访问
    
            //设置初始化参数
            bean.setInitParameters(initParams);
            return bean;
        }
    
        //2、配置一个web监控的filter
        @Bean
        public FilterRegistrationBean<WebStatFilter> webStatFilter(){
            FilterRegistrationBean<WebStatFilter> bean = new FilterRegistrationBean<>();
            bean.setFilter(new WebStatFilter());
    
            //exclusions:设置哪些请求进行过滤排除掉,从而不进行统计
            Map<String, String> initParams = new HashMap<>();
            initParams.put("exclusions", "*.js,*.css,/druid/*,/jdbc/*");
            bean.setInitParameters(initParams);
    
            //"/*" 表示过滤所有请求
            bean.setUrlPatterns(Arrays.asList("/*"));
            return bean;
        }
    }
    

    我们可以访问http://localhost:8080/druid,输入后台管理界面的账号密码访问。

    后台首页

    使用官方的starter

    1、导入官方的starter

    <dependency>
        <groupId>com.alibaba</groupId>
        <artifactId>druid-spring-boot-starter</artifactId>
        <version>1.1.10</version>
    </dependency>
    

    2、在application.yml中配置druid

    spring:
      datasource: # 数据源
        druid:
          driver-class-name: com.mysql.jdbc.Driver
          url: jdbc:mysql://localhost:3306/k0503db?useUnicode=true&characterEncoding=utf-8&serverTimezone=GMT%2B
          username: root
          password: root123
          stat-view-servlet:
            enabled: true
            login-username: admin
            login-password: 123456
            url-pattern: "/druid/*"
    

    3、启动SpringBoot主应用类,访问http://localhost:8080/druid/

    整合MyBatis

    官方文档:http://mybatis.org/spring-boot-starter/mybatis-spring-boot-autoconfigure/

    Maven仓库地址:https://mvnrepository.com/artifact/org.mybatis.spring.boot/mybatis-spring-boot-starter/2.1.2

    新建springboot项目

    <dependency>
        <groupId>org.mybatis.spring.boot</groupId>
        <artifactId>mybatis-spring-boot-starter</artifactId>
        <version>2.1.2</version>
    </dependency>
    

    Mapper+Mapper.xml

    配置数据库连接信息(不变)

    spring:
      datasource:
        username: root
        password: root123
        url: jdbc:mysql://localhost:3306/eesy_mybatis?serverTimezone=GMT%2B8&useUnicode=true&characterEncoding=utf-8
        driver-class-name: com.mysql.cj.jdbc.Driver
        type: com.alibaba.druid.pool.DruidDataSource
        #   数据源其他配置
        initialSize: 5
        minIdle: 5
        maxActive: 20
        maxWait: 60000
        timeBetweenEvictionRunsMillis: 60000
        minEvictableIdleTimeMillis: 300000
        validationQuery: SELECT 1 FROM DUAL
        testWhileIdle: true
        testOnBorrow: false
        testOnReturn: false
        poolPreparedStatements: true
        #配置监控统计拦截的filters,stat:监控统计、log4j:日志记录、wall:防御sql注入
        #如果允许时报错  java.lang.ClassNotFoundException: org.apache.log4j.Priority
        #则导入 log4j 依赖即可,Maven 地址:https://mvnrepository.com/artifact/log4j/log4j
        filters: stat,wall,log4j
        maxPoolPreparedStatementPerConnectionSize: 20
        useGlobalDataSourceStat: true
        connectionProperties: druid.stat.mergeSql=true;druid.stat.slowSqlMillis=500
    
    

    创建实体类,导入 Lombok

    <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
    </dependency>
    
    package com.coydone.pojo;
    
    import lombok.AllArgsConstructor;
    import lombok.Data;
    import lombok.NoArgsConstructor;
    
    @Data
    @NoArgsConstructor
    @AllArgsConstructor
    public class User {
        private Integer id;
        private String name;
        private String pwd;
    }
    

    创建mapper目录以及对应的 Mapper 接口UserMapper.java

    package com.coydone.mapper;
    
    import com.coydone.pojo.User;
    import org.apache.ibatis.annotations.Mapper;
    import org.springframework.stereotype.Repository;
    
    import java.util.List;
    
    //这个注解表示了这是一个mybatis的mapper类
    @Mapper
    @Repository
    public interface UserMapper {
        List<User> queryUserList();
    
        User queryUserById(int id);
    
        int addUser(User user);
    
        int updateUser(User user);
    
        int deleteUser(int id);
    
    }
    

    对应的Mapper映射文件UserMapper.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.coydone.mapper.UserMapper">
        <select id="queryUserList" resultType="User">
           select * from user ;
        </select>
    
        <select id="queryUserById" resultType="User" parameterType="int">
           select * from user where id = #{id};
        </select>
    
        <insert id="addUser" parameterType="User">
            insert into user (id,name ,pwd) values (#{id},#{name},#{pwd});
        </insert>
    
        <update id="updateUser" parameterType="User">
            update user set name=#{name},pwd=#{pwd} where id = #{id};
        </update>
    
        <delete id="deleteUser" parameterType="int">
            delete from user where id = #{id} ;
        </delete>
    
    </mapper>
    
    # 整合mybatis
    mybatis:
      type-aliases-package: com.coydone.pojo
      # 指定sql映射文件的位置
      mapper-locations: classpath:mybatis/mapper/*.xml
    

    maven配置资源过滤问题

    <resources>
        <resource>
            <directory>src/main/java</directory>
            <includes>
                <include>**/*.xml</include>
            </includes>
            <filtering>true</filtering>
        </resource>
    </resources>
    

    编写User的 UserController 进行测试

    package com.coydone.controller;
    
    @RestController
    public class UserController {
        @Autowired
        private UserMapper userMapper;
    
        @GetMapping("/queryUserList")
        public List<User> queryUserList(){
            List<User> userList = userMapper.queryUserList();
            return userList;
        }
    
        @GetMapping("/addUser")
        public String addUser(){
            userMapper.addUser(new User(2,"coydone","123"));
            return "ok";
        }
    
        @GetMapping("/updateUser")
        public String updateUser(){
            userMapper.updateUser(new User(2,"coydone","123"));
            return "ok";
        }
    
        @GetMapping("/deleteUser")
        public String deleteUser(){
            userMapper.deleteUser(2);
            return "ok";
        }
    }
    

    使用Mybatis的全局配置文件

    mybatis-config.xml

    <?xml version="1.0" encoding="UTF-8"?>
    <!DOCTYPE configuration
            PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
            "http://mybatis.org/dtd/mybatis-3-config.dtd">
    <!--mybatis的核心配置文件-->
    <configuration>
        <settings>
            <!--下划线转驼峰-->
            <setting name="mapUnderscoreToCamelCase" value="true"/>
            <!--配置日志-->
            <!--        <setting name="logImpl" value="STDOUT_LOGGING"/>-->
        </settings>
    
        <typeAliases>
            <!--扫描包:配置实体别名为实体名-->
            <package name="com.coydone.entity"></package>
        </typeAliases>
        <plugins>
            <!--分页配置-->
            <!-- 5.0  com.github.pagehelper.PageInterceptor      -->
            <!-- 4.x  com.github.pagehelper.PageHelper      -->
            <plugin interceptor="com.github.pagehelper.PageInterceptor">
                <!-- 在spring5版本不需要指定数据库 -->
                <!--            <property name="dialect" value="mysql"></property>-->
                <!-- 3.3.0版本可用 - 分页参数合理化,默认false禁用 -->
                <!-- 启用合理化时,如果pageNum<1会查询第一页,如果pageNum>pages会查询最后一页 -->
                <!-- 禁用合理化时,如果pageNum<1或pageNum>pages会返回空数据 -->
                <!--            <property name="reasonable" value="true"/>-->
            </plugin>
        </plugins>
        <mappers>
            <mapper resource="mapper/UserMapper.xml"></mapper>
        </mappers>
    </configuration>
    

    application.yml

    mybatis: 
      config-location: classpath:mybatis-config.xml
    

    注解版

    配置扫描

    方式一:在每一个Mapper上加@Mapper

    方式二:在启动类上加@MapperScan(basePackage={"com.coydone.mapper"})

    //指定这是一个操作数据库的mapper
    @Mapper
    public interface UserMapper {
    
        @Select("select * from user where id=#{id}")
        User getUsertById(Integer id);
    
        @Delete("delete from user where id=#{id}")
        int deleteUserById(Integer id);
    
        @Options(useGeneratedKeys = true,keyProperty = "id")
        @Insert("insert into user(name) values(#{name})")
        int insertUser(User user);
    
        @Update("update department set user=#{name} where id=#{id}")
        int updateUser(User user);
    }
    

    更多使用参照http://www.mybatis.org/spring-boot-starter/mybatis-spring-boot-autoconfigure/

    整合PageHelper

    <dependency>
        <groupId>com.github.pagehelper</groupId>
        <artifactId>pagehelper-spring-boot-starter</artifactId>
        <version>1.2.10</version>
    </dependency>
    

    直接使用,无需任何配置。

    配置日志

    <!--  log4j的starter-->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-log4j</artifactId>
        <version>${log4j.starter.version}</version>
    </dependency>
    
    mybatis:
      mapper-locations:   #配置Mapper.xml的地址
      - classpath:mapper/*/*.xml  
      # mybaits的sql 和参数的日志输出
      configuration:
        log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
    

    事务处理

    注解形式

    在Spring Boot中推荐使用@Transactional注解来声明事务。只需要在需要事务控制的方法或类(全部方法有效)上增加 @Transactional注解,原理是Spring Boot会自动默认分别注入DataSourceTransactionManager或JpaTransactionManager。

    @Transactional不仅可以注解在方法上,也可以注解在类上,当注解在类上的时候意味着此类的所有public方法都是开启事务的,如果类级别和方法级别同时使用了@Transactional注解,则使用在类级别的注解会重载方法级别的注解。

    service的实现类上加@Transactional的注解。

    以上的配置方法是一个一个的service去加,也可以使用AOP的切面配置方式简化配置。

    使用AOP的切面配置方式

    1、引入AOP的依赖

    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-aop</artifactId>
    </dependency>
    

    2、创建事务配置类

    @Aspect
    @Configuration
    public class TransactionAdviceConfig {
        private static final String AOP_POINTCUT_EXPRESSION = "execution (* com.***.service.*.*(..))";
        @Autowired
        private PlatformTransactionManager transactionManager;
        @Bean
        public TransactionInterceptor txAdvice() {
            DefaultTransactionAttribute txAttr_REQUIRED = new DefaultTransactionAttribute();
            txAttr_REQUIRED.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED);
            DefaultTransactionAttribute txAttr_REQUIRED_READONLY = new DefaultTransactionAttribute();
            txAttr_REQUIRED_READONLY.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED);
            txAttr_REQUIRED_READONLY.setReadOnly(true);
            NameMatchTransactionAttributeSource source = new NameMatchTransactionAttributeSource();
            source.addTransactionalMethod("add*", txAttr_REQUIRED);
            source.addTransactionalMethod("save*", txAttr_REQUIRED);
            source.addTransactionalMethod("delete*", txAttr_REQUIRED);
            source.addTransactionalMethod("update*", txAttr_REQUIRED);
            source.addTransactionalMethod("exec*", txAttr_REQUIRED);
            source.addTransactionalMethod("set*", txAttr_REQUIRED);
            source.addTransactionalMethod("get*", txAttr_REQUIRED_READONLY);
            source.addTransactionalMethod("query*", txAttr_REQUIRED_READONLY);
            source.addTransactionalMethod("find*", txAttr_REQUIRED_READONLY);
            source.addTransactionalMethod("list*", txAttr_REQUIRED_READONLY);
            source.addTransactionalMethod("count*", txAttr_REQUIRED_READONLY);
            source.addTransactionalMethod("is*", txAttr_REQUIRED_READONLY);
            return new TransactionInterceptor(transactionManager, source);
        }
        @Bean
        public Advisor txAdviceAdvisor() {
            AspectJExpressionPointcut pointcut = new AspectJExpressionPointcut();
            pointcut.setExpression(AOP_POINTCUT_EXPRESSION);
            return new DefaultPointcutAdvisor(pointcut, txAdvice());
        }
    }
    
    coydone的博客
  • 相关阅读:
    剑指offer:合并两个排序的链表
    剑指offer:调整数组顺序使奇数位于偶数前面
    剑指offer:链表中倒数第K个结点
    牛客网在线编程:末尾0的个数
    剑指offer7:数值的整数次方
    牛客网在线编程:计算糖果
    牛客网在线编程:求数列的和
    牛客网在线编程:公共字符
    剑指offer7:斐波那契数列
    Qt入门之常用qt控件认知之Button系列
  • 原文地址:https://www.cnblogs.com/coydone/p/13821285.html
Copyright © 2011-2022 走看看