spring boot学习与实践练习3
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;
}
-
、监控运行效果
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 是负责执行底层映射语句的内部对象。