spring boot学习与实践练习3

2020-11-13  本文已影响0人  机灵鬼鬼

Spring Boot与数据访问

对于数据访问层,无论是SQL还是NOSQL,spring boot默认采用整合Spring Data的方式进行统一处理,添加当量的自动配置,屏蔽了很多设置。引入各种xxxTemplate,xxxRepository来简化我们对数据访问层的操作,对我们来说只需要进行简单的设置即可。我们将在数据访问章节测试使用SQL相关、NOSQL在缓存、消息、检索等章节测试。
---JDBC
---MyBatis
---JPA

整合JDBC与数据源

1)、引入jar包

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

2)、添加数据参数配置

spring:
  datasource:
    url: jdbc:mysql://192.168.19.xxx:3306/omso2o
    username: omso2o
    password: omso2o
    driver-class-name: com.mysql.jdbc.Driver

3)、配置效果:com.zaxxer.hikari.util.DriverDataSource 数据源
数据源的相关配置都在DataSourceProperties类里面,spring boot关于自动装配数据源的处理都在org.springframework.boot.autoconfigure.jdbc包下面。
自动配置原理:参考DataSourceConfiguration,使用spring.datasource.type来指定数据源类型。
spring boot可是支持的数据源很多默认支持:
org.apache.tomcat.jdbc.pool.DataSource
com.zaxxer.hikari.HikariDataSource
org.apache.commons.dbcp2.BasicDataSource
也可以通过spring.datasource.type来制定自己的数据源:
4)、自定义数据源类型

@Configuration(proxyBeanMethods = false)
@ConditionalOnMissingBean(DataSource.class)
@ConditionalOnProperty(name = "spring.datasource.type")
static class Generic {

    @Bean
    DataSource dataSource(DataSourceProperties properties) {
//使用DataSourceBuilder来创建数据源,利用反射创建相应type的数据源,并且绑定相关属性。
        return properties.initializeDataSourceBuilder().build();
    }

}

5)、DataSourceInitializer
5.1)createSchema:创建sql表语句
5.2)runScripts:运行sql脚本的语句
5.3)getScripts:获取脚本文件,只要文件满足存放位置和命名规则

classpath:schema-.sql或data-.sql
classpath
:schema.sql或者data.sql
5.4)可以使用schema:
- classpath: schema-bb.sql
- classpath: schema-aa.sql
制定sql脚本文件的位置
6)、操作数据库:自动配置了JdbcTemplate操作数据库
我们写个测试类

    @Test
    public void getJdbcConnection() throws Exception{
//        Connection cn= dataSource.getConnection();
        DataSource ds=jdbcTemplate.getDataSource();
        System.out.println("数据库链接:"+ds.getConnection());

        List<Map<String, Object>> list= jdbcTemplate.queryForList("select * from data_basic_product limit 10");
        System.out.println(list);
    }

结果:

image.png
7)、切换数据源(druid为例)
7.1)、引入druid依赖(去maven官网https://search.maven.org/)
<dependency>
  <groupId>com.alibaba</groupId>
  <artifactId>druid</artifactId>
  <version>1.2.3</version>
</dependency>

7.2)、修改spring boot数据源配置(spring.datasource.type)

spring: 
  datasource:
    url: jdbc:mysql://192.168.19.xx:3306/omso2o
    username: omso2o
    password: omso2o
    driver-class-name: com.mysql.jdbc.Driver
    type: com.alibaba.druid.pool.DruidDataSource

7.3)、测试下是否能正常获取数据

   @Test
    public void getJdbcConnection() throws Exception{
//        Connection cn= dataSource.getConnection();
        DataSource ds=jdbcTemplate.getDataSource();
        System.out.println("数据库链接:"+ds.getConnection());

        List<Map<String, Object>> list= jdbcTemplate.queryForList("select * from data_basic_product limit 10");
        System.out.println(list);
    }

7.4)、数据源链接的系列配置参数
DruidDataSource的配置说明:
https://github.com/alibaba/druid/wiki/DruidDataSource%E9%85%8D%E7%BD%AE%E5%B1%9E%E6%80%A7%E5%88%97%E8%A1%A8

要想让我们添加的配置参数生效,必须自定义DruidDataSource的配置类,让他去读yml的配置参数

server:
  port: 8083
spring:
  datasource:
    url: jdbc:mysql://192.168.19.xx:3306/omso2o
    username: omso2o
    password: omso2o
    driver-class-name: com.mysql.jdbc.Driver
    type: com.alibaba.druid.pool.DruidDataSource
##以下属性都是默认的datasoruce不能获取到的属性,需要自定义方式为datasource赋值
    initialSize: 5
    maxActive: 100
    minIdle: 5
    maxIdle: 8
    maxWati: 60000
    minEvictableIdleTimeMillis: 300000
    maxPoolPreparedStatementPerConnectionSize: 100
    poolPreparedStatements: true
    validationQuery: select 1 for dual
    testOnBorrow: false
    testOnReturn: false
    testWhileIdle: true
    filters: stat,wall
@Configuration
public class MyDataSourceConfiguration {
    @ConfigurationProperties(prefix = "spring.datasource")
    @Bean
    public DataSource druidDataSource(){
        return new DruidDataSource();
    }
}

如何使用Druid的监控

1)、配置Druid管理后台的servlet

 //配置Druid的监控
    //1、配置一个管理后台的servlet
    @Bean
    public ServletRegistrationBean statViewServlet(){
        ServletRegistrationBean bean=new ServletRegistrationBean(new StatViewServlet(),"/druid/*");
        Map<String,String> initParms=new HashMap<>();
        initParms.put("loginUsername","ls");
        initParms.put("loginPassword","123456");
        initParms.put("allow","192.168.23.xx");//允许访问的域名,默认是全部允许
        bean.setInitParameters(initParms);
        return bean;
    }

2)、配置一个监控filter

   //2、配置web监控的filter
    @Bean
    public FilterRegistrationBean webStatFilter(){
        FilterRegistrationBean bean=new FilterRegistrationBean(new WebStatFilter());
        Map<String,String> initParms=new HashMap<>();
        initParms.put("exclusions","/druid/*,*.js,*.css,*.html");//不拦截的请求
        bean.setInitParameters(initParms);
        bean.setUrlPatterns(Arrays.asList("/*"));
        return bean;

    }
  1. 、监控运行效果


    image.png

整合myBatis

1)、添加依赖

<dependency>
   <groupId>mysql</groupId>
   <artifactId>mysql-connector-java</artifactId>
   <scope>runtime</scope>
</dependency>

2)、添加数据表映射类

public class DataChannel {
    private int id;
    private String cPlatform;
    private String cPlatcode;
    private String cPlatformname;
    private String cChannelid;
    private String cChannelname;
    private String cScaleStock;
。。。。。。省略get、set方法
}

3)、编写数据操作的Mapper接口

/**
 * 操作数据库表的mapper
 */
@Mapper
public interface DataChannelMapper {

    @Select("select * from data_channel where id=#{id}")
    public DataChannel getDataChannelById(int id);

    @Select("delete from data_channel where id=#{id}")
    public int deleteDataChannelById(int id);

    @Options(useGeneratedKeys = true,keyProperty = "id")
    @Insert("insert into data_channel(c_platform,c_platcode,c_platformname,c_channelid,c_channelname,c_scale_stock) " +
            "values(#{cPlatform},#{cPlatcode},#{cPlatformname},#{cChannelid},#{cChannelname},#{cScaleStock})")
    public int insertDataChannel(DataChannel dataChannel);

    @Update("update data_channel set c_platformname=#{cPlatformname} where id=#{id}")
    public int updateDataChannel(DataChannel dataChannel);
}

4)、指定驼峰命名法映射:实现bean属性和表字段的映射
//自定义mbatis的配置规则:为容器添加ConfigurationCustomizer
@Configuration
public class MyBatisConfig {

@Bean
public ConfigurationCustomizer configurationCustomizer(){
    return new ConfigurationCustomizer(){

        @Override
        public void customize(org.apache.ibatis.session.Configuration configuration) {
            //使用驼峰解析
            configuration.setMapUnderscoreToCamelCase(true);
        }
    };
}

}

5)、如果我们类里面有大量的mapper配置接口,如果都写@Mapper有些麻烦,能否批量设置?
//扫描com.ls.demo.mapper包下的所有mapper类
@MapperScan(value = "com.ls.demo.mapper")
可以注解在spring boot的入口程序,也可以注解在MyBatisConfig类上,我们这里注解在了MyBatisConfig,

6)、配置文件方式的怎么做?
6.1)、编写mapp接口类

/**
 * 操作数据库表的mapper
 */
@Mapper
public interface DataChannelMapper {

    public DataChannel getDataChannelById(int id);

    public int insertDataChannel(DataChannel dataChannel);

    public int updateDataChannel(DataChannel dataChannel);
}

6.2)、配置文件编写映射mapper类

<?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.ls.demo.mapper.DataChannelMapper">
   <select id="getDataChannelById" resultType="com.ls.demo.domain.DataChannel">
       select * from data_channel where id = #{id}
   </select>

   <insert id="insertDataChannel" useGeneratedKeys="true" keyProperty="id">
           insert into data_channel(c_platform,c_platcode,c_platformname,c_channelid,c_channelname,c_scale_stock)
           values(#{cPlatform},#{cPlatcode},#{cPlatformname},#{cChannelid},#{cChannelname},#{cScaleStock})
   </insert>
</mapper>

6.3)、mybatis全局配置文件,配置数据库字段与属性文件映射
他有很多配置属性详细参看:https://mybatis.org/mybatis-3/zh/configuration.html#settings

<?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">
<configuration>
    <settings>
        <!-- 是否开启驼峰命名自动映射,即从经典数据库列名 A_COLUMN 映射到经典 Java 属性名 aColumn。 -->
        <setting name="mapUnderscoreToCamelCase" value="true"/>
    </settings>
</configuration>

6.4)、让spring boot扫描到mybatis配置文件

mybatis:
  mapper-locations: classpath:mybatis/mapper/*.xml
  config-location: classpath:mybatis/mybatis-config.xml

6.5)、拦截sql语句的方式
6.5.1)DataSource拦截(以Druid为例,推荐使用这个方案)
参考druid官网配置:防火墙WallFilter:https://github.com/alibaba/druid/wiki/%E9%85%8D%E7%BD%AE-wallfilter

@Configuration
public class MyDataSourceConfiguration {
    @Autowired
    WallFilter wallFilter;

    @ConfigurationProperties(prefix = "spring.datasource")
    @Bean
    public DataSource druidDataSource(){//注入自定义的数据源
        DruidDataSource dataSource= new DruidDataSource();
        dataSource.getProxyFilters().add(wallFilter);//如果要用Druid的防火墙控制,就要为他的代理ProxyFilters为DataSource的filters赋值
        return dataSource;
    }
    @Bean(name = "wallConfig")
    WallConfig wallFilterConfig() {
        WallConfig wc = new WallConfig();
        wc.setMultiStatementAllow(true);//是否允许一次执行多条语句,缺省关闭
        wc.setTruncateAllow(false);//truncate语句是危险,缺省打开,若需要自行关闭
        wc.setDeleteWhereAlwayTrueCheck(true);//检查DELETE语句的WHERE子句是否是一个永真条件
        wc.setDeleteWhereNoneCheck(true);//检查DELETE语句是否无where条件,这是有风险的,但不是SQL注入类型的风险
        wc.setUpdateWhereAlayTrueCheck(true);//检查UPDATE语句的WHERE子句是否是一个永真条件
        wc.setUpdateWhereNoneCheck(true);//检查UPDATE语句是否无where条件,这是有风险的,但不是SQL注入类型的风险
        return wc;
    }

    @Bean(name = "wallFilter")
    @DependsOn("wallConfig")
    WallFilter wallFilter(WallConfig wallConfig) {
        WallFilter wfilter = new WallFilter();
        wfilter.setConfig(wallConfig);
        wfilter.setDbType(DbType.mysql);
        return wfilter;
    }
    //配置Druid的监控
    //1、配置一个管理后台的servlet
    @Bean
    public ServletRegistrationBean statViewServlet(){
        ServletRegistrationBean bean=new ServletRegistrationBean(new StatViewServlet(),"/druid/*");
        Map<String,String> initParms=new HashMap<>();
        initParms.put("loginUsername","ls");
        initParms.put("loginPassword","123456");
        initParms.put("allow","10.28.143.247");//允许访问的域名,默认是全部允许
        bean.setInitParameters(initParms);
        return bean;
    }
    //2、配置web监控的filter
    @Bean
    public FilterRegistrationBean webStatFilter(){
        FilterRegistrationBean bean=new FilterRegistrationBean(new WebStatFilter());
        Map<String,String> initParms=new HashMap<>();
        initParms.put("exclusions","/druid/*,*.js,*.css,*.html");//不拦截的请求
        bean.setInitParameters(initParms);
        bean.setUrlPatterns(Arrays.asList("/*"));
        return bean;
    }

}

6.5.2)mybatis拦截
详情查看mybatis的官网:https://mybatis.org/mybatis-3/zh/configuration.html#plugins
1)、通过 MyBatis 提供的强大机制,使用插件是非常简单的,只需实现 Interceptor 接口,并指定想要拦截的方法签名即可。

package com.ls.demo.plugin;

import org.apache.ibatis.builder.StaticSqlSource;
import org.apache.ibatis.executor.Executor;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.mapping.SqlCommandType;
import org.apache.ibatis.plugin.Interceptor;
import org.apache.ibatis.plugin.Intercepts;
import org.apache.ibatis.plugin.Invocation;
import org.apache.ibatis.plugin.Signature;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.Properties;

/**
 * 监控mybatis的执行语句动作,这里的除了select不拦截,
 * 其他语句包涵update、delete、insert都进行拦截
 */
@Intercepts(
        {@Signature(
        type= Executor.class,
        method = "update",
        args = {MappedStatement.class,Object.class}
        )
})
public class MyBatisIntercept implements Interceptor {
    Logger logger=LoggerFactory.getLogger(MyBatisIntercept.class);
    private Properties properties = new Properties();
    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        logger.info("sql拦截业务模块:" + properties.getProperty("busiModel"));
        // 可以添加sql执行前的逻辑操作
        MappedStatement mst = (MappedStatement) invocation.getArgs()[0];//sql执行计划
        if (SqlCommandType.UPDATE.equals(mst.getSqlCommandType()) ||
                SqlCommandType.DELETE.equals(mst.getSqlCommandType())) {
            //invocation.getArgs()[1];//参数对象
            String sql = mst.getSqlSource().getBoundSql(invocation.getArgs()[1]).getSql().toUpperCase();
            String condKey = "WHERE";
            String errmsg="危险语句:【存在全表"+mst.getSqlCommandType()+"风险】"+sql;
            if (sql.contains(condKey)) {//如果有where条件,要避免恒等式
                String rest = sql.substring(sql.indexOf(condKey) + condKey.length());
                rest = rest.replace(" ", "").replace("\n", "");
                if ("1=1".equals(rest)) {
                    logger.info(errmsg);
                    throw new RuntimeException(errmsg );
                } else if ("TRUE".equals(rest)) {
                    logger.info(errmsg);
                    throw new RuntimeException(errmsg);
                }
            } else {//没有where条件就要拒绝
                logger.info(errmsg);
                throw new RuntimeException(errmsg);
            }
        }
        Object returnObject = invocation.proceed();
        // 可以添加sql执行后的逻辑操作
        return returnObject;
    }

    public Properties getProperties() {
        return properties;
    }

    @Override
    public void setProperties(Properties properties) {
        this.properties = properties;
    }

}

2)、添加配置(在mybatis-config.xml中添加)

<!-- mybatis-config.xml -->
<plugins>
  <plugin interceptor="com.ls.demo.plugin.MyBatisIntercept">
    <property name="busiModel" value="100"/>
  </plugin>
</plugins>

上面的插件将会拦截在 Executor 实例中所有的 “update” 方法调用, 这里的 Executor 是负责执行底层映射语句的内部对象。

上一篇 下一篇

猜你喜欢

热点阅读