MyBatis是如何让我们通过接口就能调用到SQL的
大致可分为如下几个步骤1. 动态注册bean1.1 根据配置mapperScan, 扫描对应的包, 将对应的类解析成BeanDefinition1.2 通过替换BeanDefinition中的BeanClass为MapperFactoryBean, (原来的BeanClass是Mapper接口) 实现了在spring生成对应的对象时, 返回的对象不是本身类型的对象,而是MapperFactoryBean重写FactoryBean接口的getObject()方法返回的代理对象。该方法getObject()已经对mapper接口进行了代理, 即后续进行自动注入时, 也是返回getObject()生成的代理对象2. 生成对应的代理对象2.1 在getObject()方法中, 会获取到接口的全限定名称, 然后进一步对代理方法进行封装, 调用链如下MapperFactoryBean:
public T getObject() throws Exception {
return getSqlSession().getMapper(this.mapperInterface);
}
DefaultSqlSession:
public <T> T getMapper(Class<T> type) {
//configuration是mybatis的重要配置类, 在初始化的时候, 就会将mapper接口添加到configuration中
return configuration.getMapper(type, this);
}
Configuration:
public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
return mapperRegistry.getMapper(type, sqlSession);
}
MapperRegistry:
public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
//获取mapper代理工厂
final MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory<T>) knownMappers.get(type);
if (mapperProxyFactory == null) {
throw new BindingException("Type " + type + " is not known to the MapperRegistry.");
}
try {
return mapperProxyFactory.newInstance(sqlSession);
} catch (Exception e) {
throw new BindingException("Error getting mapper instance. Cause: " + e, e);
}
}
MapperProxyFactory:
protected T newInstance(MapperProxy<T> mapperProxy) {
return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);
}
public T newInstance(SqlSession sqlSession) {
//返回一个新代理对象
final MapperProxy<T> mapperProxy = new MapperProxy<>(sqlSession, mapperInterface, methodCache);
return newInstance(mapperProxy);
}2.2 在org.apache.ibatis.binding.MapperProxy#cachedInvoker中, new PlainMethodInvoker(new MapperMethod(mapperInterface, method, sqlSession.getConfiguration())), 并且将代理方法调用器缓存起来. MapperMethod该对象即是最终调用方法的对象.3. 执行对应的方法3.1 方法调用, 代理里最常见的方法invokepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
try {
//过滤掉object的方法
if (Object.class.equals(method.getDeclaringClass())) {
return method.invoke(this, args);
} else {
return cachedInvoker(method).invoke(proxy, method, args, sqlSession);
}
} catch (Throwable t) {
throw ExceptionUtil.unwrapThrowable(t);
}
}3.2. org.apache.ibatis.binding.MapperProxy.PlainMethodInvoker重写的invoke方法中,判断该方法是否被调用过(是否存在于缓存), 若没有, 则创建一个PlainMethodInvoker方法调用器, 传入MapperMethod(MapperMethod是真正执行方法的对象), 并将新创建的PlainMethodInvoker存入缓存中(methodCache), 并调用该PlainMethodInvoker的invoke方法,3.3. xml中的id和select等标签封装成了SqlCommand, 调用mapperMethod的execute, 执行对应的增删改查.4. 结果集封装, 进行一些数据库数据对应java对象的转换通过mybatis的封装和代理, 将mapper.xml转换成了接口的实例对象如有谬误, 欢迎斧正简化版如下: https://blog.csdn.net/sinat_25991865/article/details/89891581public interface UserMapper {
List<SysUser> selectAll();
}public class MyMapperProxy<T> implements InvocationHandler {
private Class<T> mapperInterface;
private SqlSession sqlSession;
public MyMapperProxy(Class<T> mapperInterface, SqlSession sqlSession) {
this.mapperInterface = mapperInterface;
this.sqlSession = sqlSession;
}
@Override
public Object invoke(Object proxy , Method method , Object[] args)
throws Throwable {
//针对不同的 sql 类型,需要调用sqlSession不同的方法
//接口方法中的参数也有很多情况 ,这里只考虑没有有参数的情况
List<T> list= sqlSession.selectList(
mapperInterface.getCanonicalName() + ”.” + method.getName());
//返回数据也有很多情况,这里不做处理直接返回
return list;
}
}方法调用//获取sqlSession
SqlSession sqlSession = getSqlSession();
//获取 UserMapper 接口
MyMapperProxy userMapperProxy = new MyMapperProxy(
UserMapper.class , sqlSession) ;
UserMapper userMapper = (UserMapper) Proxy.newProxyinstance (
Thread.currentThread().getContextClassLoader(),
new Class[ ] {UserMapper.class},
userMapperProxy) ;
//调 用 selectAll 方 法
List<SysUser> user= userMapper.selectAll();
Java SPI机制与应用
SPI全称Service Provider Interface, 是Java提供的一套用来被第三方实现或者扩展的接口实际上是"基于接口的编程+策略模式+配置文件"组合实现的动态加载机制, 更是设计模式的生动体现它可以用来启用框架扩展和替换组件. SPI的作用就是为这些被扩展的API寻找服务实现我们一般推荐模块之间基于接口编程, 模块之间不对实现类进行硬编码, 一旦代码里涉及具体的实现类, 就违反了可拔插的原则, 如果需要替换一种实现, 就需要修改代码简单demopublic interface UploadFile {
void upload(String file);
}
public class AliyunCDN implements UploadFile {
@Override
public void upload(String url) {
System.out.println("upload to AliyunCDN");
}
}
public class QiniuCDN implements UploadFile {
@Override
public void upload(String url) {
System.out.println("upload to QiniuCDN");
}
}然后需要在resources目录下新建META-INF/services目录, 并且在这个目录下新建一个与上述接口的全限定名一致的文件, 在这个文件中写入接口的实现类的全限定名org.test.AliyunCDN
org.test.QiniuCDN启动类public class Test {
public static void main(String[] args) {
ServiceLoader<UploadFile> uploadFiles = ServiceLoader.load(UploadFile.class);
for (UploadFile u : uploadFiles) {
u.upload("filePath");
}
}
}项目结构C:
├─pom.xml
└─src
└─main
├─java
│ └─org
│ └─test
│ AliyunCDN.java
│ QiniuCDN.java
│ Test.java
│ UploadFile.java
└─resources
└─META-INF
└─services
org.test.UploadFile运行 upload to AliyunCDN
upload to QiniuCDN使用场景Spring Spring中大量使用了SPI, 比如: 对Servlet3.0规范对ServletContainerInitializer的实现(Tomcat加载SpringBoot项目的War包)/自动类型转换Type Conversion SPI(Converter SPI/Formatter SPI)等SpringBoot SpringBoot中spring.factories和SpringFactoriesLoaderHutool拼音工具-PinyinUtil 官方文档和其它门面模块类似,采用SPI方式识别所用的库。例如你想用Pinyin4j,只需引入jar,Hutool即可自动识别。Dubbo Dubbo中也大量使用SPI的方式实现框架的扩展, 不过它对Java提供的原生SPI做了封装,允许用户扩展实现Filter接口JDBC JDBC4.0以后不再需要Class.forName即可注册驱动原创声明,本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
SpringBoot读写分离配置与事务
引入依赖<dependency>
<groupId>com.baomidou</groupId>
<artifactId>dynamic-datasource-spring-boot-starter</artifactId>
<version>3.5.1</version>
</dependency>跟mybatis-plus属于同一个开源组织 苞米豆配置文件spring:
datasource:
dynamic:
primary: master
# 严格匹配数据源,默认false. true未匹配到指定数据源时抛异常,false使用默认数据源
strict: true
datasource:
master:
url: jdbc:mysql://192.168.101.128:3307/test?useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai
username: root
password: 123456
slave:
url: jdbc:mysql://192.168.101.128:3308/test?useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai
username: root
password: 123456
# 按需开启日志
logging:
level:
com.baomidou.dynamic: debug方法或类上加上@DS注解即可切换数据源该框架获取数据库连接的核心逻辑是以下这段在被DS注解标记的方法上, 会被此拦截器拦截, 获取到注解上定义的值, 并存入栈结构中com.baomidou.dynamic.datasource.aop.DynamicDataSourceAnnotationInterceptorpublic Object invoke(MethodInvocation invocation) throws Throwable {
String dsKey = determineDatasourceKey(invocation);
//获取注解值入栈
DynamicDataSourceContextHolder.push(dsKey);
try {
return invocation.proceed();
} finally {
//方法结束后出栈
DynamicDataSourceContextHolder.poll();
}
}然后com.baomidou.dynamic.datasource.ds.AbstractRoutingDataSource#getConnection()public Connection getConnection() throws SQLException {
String xid = TransactionContext.getXID();
if (StringUtils.isEmpty(xid)) {
return determineDataSource().getConnection();
} else {
//获取栈顶的一个值
String ds = DynamicDataSourceContextHolder.peek();
ds = StringUtils.isEmpty(ds) ? "default" : ds;
//根据值获取对应数据库的连接
ConnectionProxy connection = ConnectionFactory.getConnection(ds);
return connection == null ? getConnectionProxy(ds, determineDataSource().getConnection()) : connection;
}
}可以发现框架是通过维护一个栈结构进行对应数据源的切换, 类似方法的栈, 因为方法间可能嵌套调用, 所以使用此结构便于管理但Spring的@Transactional会影响@DS例如@Autowired
@Lazy
CurrentService currentService;
@DS("master")
@Transactional
public void updateUser() {
baseMapper.updateById(user);
System.out.println(currentService.get());
}
@DS("slave")
public User get() {
return baseMapper.selectById(1);
}在这里, master和slave是使用binlog搭建的读写分离架构但实际get方法却能读取到updateUser所做的修改, 通过Debug也能看到真正的数据库连接属性, get方法还是使用的master库因为在Spring管理下, 获取到数据库连接后, 会和当前线程进行绑定, 如果后面的方法被判断为不需要新建连接, 则复用之前与线程绑定的连接, 那么即使有DS注解, 也切换不了库如何判断需不需要新建连接? 看被调用方法是否定义了事务传播属性.在org.springframework.transaction.support.AbstractPlatformTransactionManager#getTransaction方法中if (isExistingTransaction(transaction)) {
//找到现有事务 -> 检查传播行为以了解行为方式
//当前已经存在一个事务
return handleExistingTransaction(def, transaction, debugEnabled);
}继续调用org.springframework.transaction.support.AbstractPlatformTransactionManager#handleExistingTransaction//判断当前方法的隔离属性是否为PROPAGATION_REQUIRES_NEW
if (definition.getPropagationBehavior() == TransactionDefinition.PROPAGATION_REQUIRES_NEW) {
if (debugEnabled) {
logger.debug("Suspending current transaction, creating new transaction with name [" + definition.getName() + "]");
}
SuspendedResourcesHolder suspendedResources = suspend(transaction);
try {
return startTransaction(definition, transaction, debugEnabled, suspendedResources);
}
catch (RuntimeException | Error beginEx) {
resumeAfterBeginException(transaction, suspendedResources, beginEx);
throw beginEx;
}
}继续调用org.springframework.transaction.support.AbstractPlatformTransactionManager#startTransactionprivate TransactionStatus startTransaction(TransactionDefinition definition, Object transaction,
boolean debugEnabled, @Nullable SuspendedResourcesHolder suspendedResources) {
boolean newSynchronization = (getTransactionSynchronization() != SYNCHRONIZATION_NEVER);
DefaultTransactionStatus status = newTransactionStatus(definition, transaction, true, newSynchronization, debugEnabled, suspendedResources);
doBegin(transaction, definition);
prepareSynchronization(status, definition);
return status;
}继续调用org.springframework.jdbc.datasource.DataSourceTransactionManager#doBeginif (!txObject.hasConnectionHolder() || txObject.getConnectionHolder().isSynchronizedWithTransaction()) {
//获取当前的数据源, 此处才能让@DS注解生效
Connection newCon = obtainDataSource().getConnection();
if (logger.isDebugEnabled()) {
logger.debug("Acquired Connection [" + newCon + "] for JDBC transaction");
}
//将数据库连接绑定到事务
txObject.setConnectionHolder(new ConnectionHolder(newCon), true);
}所以如果想让get方法读取从库, 则需要定义传播属性以便让Spring建立新连接第二种方法就是使用该框架的@DSTransactional该方法也会进行事务管理, 但功能比较简陋这个注解还提供了一个本地事务的功能: 解决多数据源的事务问题.但这个功能也有问题, 不建议使用看这个方法 com.baomidou.dynamic.datasource.tx.ConnectionFactory#notifypublic static void notify(Boolean state) {
try {
Map<String, ConnectionProxy> concurrentHashMap = CONNECTION_HOLDER.get();
//获取当前线程所有的数据库连接, 通知其进行回滚/提交, 可能存在某一个事务提交成功, 某一事务提交失败.
//并不能保证最终一致性
for (ConnectionProxy connectionProxy : concurrentHashMap.values()) {
connectionProxy.notify(state);
}
} finally {
CONNECTION_HOLDER.remove();
}
}对于这种多库事务, 建议使用Seata或消息队列贴心的是, 框架还与Seata进行了整合引入依赖<dependency>
<groupId>io.seata</groupId>
<artifactId>seata-spring-boot-starter</artifactId>
<version>1.4.2</version>
</dependency>spring:
datasource:
dynamic:
#seata1.0之后支持自动代理 这里直接配置true
seata: true
seata-server 端数据库连接用户、业务数据库 undo_log 所属用户都需要什么权限?
seata-server 端数据库连接用户、业务数据库 undo_log 所属用户都需要什么数据库权限?
jdbc连接es查询的时候,有遇到下面这种情况的大神嘛?
jdbc连接es查询的时候,有遇到下面这种情况的大神嘛? java.sql.SQLInvalidAuthorizationSpecException: current license is non-compliant for [jdbc]
Ubuntu设置MySQL远程访问
一、设置mysql远程访问 -> 第一种方式1. 编辑mysql配置文件,把其中bind-address = 127.0.0.1注释了sudo vi /etc/mysql/mysql.conf.d/mysqld.cnf使用root进入mysql命令行,执行如下2个命令,示例中mysql的root账号和密码:(我的账号密码是root 123456,如果未初始化密码,建议先设置一下,默认的太麻烦了,更改默认密码我也是尝试了很长时间,写了一篇博客参考)设置密码的博客:https://blog.csdn.net/weixin_45525272/article/details/107774685在MySQL中按照模板要求输入语句grant all on *.* to root@'%' identified by '123456' with grant option;
flush privileges;
重启mysql/etc/init.d/mysql restart二、设置mysql远程访问 -> 第二种方式编辑mysql配置文件,把其中bind-address = 127.0.0.1注释了vi /etc/mysql/mysql.conf.d/mysqld.cnf使用root进入mysql命令行,执行如下2个命令,示例中mysql的root账号密码:root mysql –u root -p
mysql> use mysql;
mysql> update user set host = '%' where user = 'root';
mysql> flush privileges;
第一句是以权限用户root登录第二句:选择mysql库第三句:修改host值(以通配符%的内容增加主机/IP地址),当然也可以直接增加IP地址第四句:刷新MySQL的系统权限相关表3. 重启mysql/etc/init.d/mysql restart三、Windows上MySQL管理器下载与远程访问使用方法下载与安装MySQL安装的详细步骤与Navicat for MySQL 安装软件链接:https://pan.baidu.com/s/1pYUXLBNtqqEQUv4anhdvIA提取码:w9i1使用点击连接选择MySQL写上初始连接配置点击连接测试查看是否成功点击左侧数据库连接就可以使用了
是不是只要支持JDBC / ODBC协议的客户端恐惧,PolarDB-X可通过相关工具的客户端访问?
是不是只要支持JDBC / ODBC协议的客户端恐惧,PolarDB-X都可以通过相关工具的客户端进行访问?
「Spring」认证安全架构指南
本指南是 Spring Security 的入门指南,提供对框架设计和基本构建块的深入了解。我们仅涵盖应用程序安全的基础知识。但是,这样做,我们可以清除使用 Spring Security 的开发人员遇到的一些困惑。为此,我们通过使用过滤器,更一般地,通过使用方法注解,来看看在 Web 应用程序中应用安全性的方式。当您需要深入了解安全应用程序的工作原理、如何对其进行自定义或需要学习如何考虑应用程序安全性时,请使用本指南。本指南并非旨在作为解决最基本问题的手册或秘诀(这些问题还有其他来源),但它对初学者和专家都可能有用。Spring Boot 也经常被引用,因为它为安全应用程序提供了一些默认行为,并且了解它如何与整体架构相适应会很有用。所有原则同样适用于不使用 Spring Boot 的应用程序。身份验证和访问控制应用程序安全性归结为两个或多或少独立的问题:身份验证(你是谁?)和授权(你可以做什么?)。有时人们会说“访问控制”而不是“授权”,这可能会让人感到困惑,但这样想可能会有所帮助,因为“授权”在其他地方被重载了。Spring Security 的架构旨在将身份验证与授权分开,并为两者提供策略和扩展点。验证认证的主要策略接口是AuthenticationManager,它只有一个方法:public interface AuthenticationManager {
Authentication authenticate(Authentication authentication)
throws AuthenticationException;
}An可以在其方法AuthenticationManager中做三件事之一:authenticate()如果它可以验证输入代表一个有效的主体,则返回一个Authentication(通常带有)。authenticated=trueAuthenticationException如果它认为输入代表无效的委托人,则抛出一个。null如果它不能决定返回。AuthenticationException是运行时异常。它通常由应用程序以通用方式处理,具体取决于应用程序的样式或用途。换句话说,通常不期望用户代码来捕获和处理它。例如,Web UI 可能会呈现一个说明身份验证失败的页面,并且后端 HTTP 服务可能会发送一个 401 响应,WWW-Authenticate根据上下文是否有标头。最常用的实现AuthenticationManager是ProviderManager,它委托给一个AuthenticationProvider实例链。AnAuthenticationProvider有点像 an AuthenticationManager,但它有一个额外的方法允许调用者查询它是否支持给定的Authentication类型:public interface AuthenticationProvider {
Authentication authenticate(Authentication authentication)
throws AuthenticationException;
boolean supports(Class<?> authentication);
}方法中的Class<?>参数supports()是真的Class<? extends Authentication>(只询问它是否支持传递给authenticate()方法的东西)。AProviderManager可以通过委托给AuthenticationProviders. 如果 aProviderManager不能识别特定的Authentication实例类型,则会跳过它。AProviderManager有一个可选的父级,如果所有提供者都返回,它可以咨询它null。如果父级不可用,则null Authentication结果为AuthenticationException.有时,应用程序具有受保护资源的逻辑组(例如,与路径模式匹配的所有 Web 资源,例如/api/**),并且每个组都可以有自己的专用AuthenticationManager. 通常,它们中的每一个都是一个ProviderManager,并且它们共享一个父级。然后,父级是一种“全局”资源,充当所有提供者的后备。图 1. 使用的AuthenticationManager层次结构ProviderManager自定义身份验证管理器Spring Security 提供了一些配置助手来快速获取应用程序中设置的常见身份验证管理器功能。最常用的帮助程序是AuthenticationManagerBuilder,它非常适合设置内存、JDBC 或 LDAP 用户详细信息或添加自定义UserDetailsService. 以下示例显示了一个配置全局(父)的应用程序AuthenticationManager:@Configuration
public class ApplicationSecurity extends WebSecurityConfigurerAdapter {
... // web stuff here
@Autowired
public void initialize(AuthenticationManagerBuilder builder, DataSource dataSource) {
builder.jdbcAuthentication().dataSource(dataSource).withUser("dave")
.password("secret").roles("USER");
}
}此示例与 Web 应用程序相关,但 的使用AuthenticationManagerBuilder更广泛(有关如何实现 Web 应用程序安全性的更多详细信息,请参阅Web 安全性)。请注意,它AuthenticationManagerBuilder是@Autowired在 a 中的一个方法中@Bean?——这就是它构建全局(父)的原因AuthenticationManager。相反,请考虑以下示例:@Configuration
public class ApplicationSecurity extends WebSecurityConfigurerAdapter {
@Autowired
DataSource dataSource;
... // web stuff here
@Override
public void configure(AuthenticationManagerBuilder builder) {
builder.jdbcAuthentication().dataSource(dataSource).withUser("dave")
.password("secret").roles("USER");
}
}如果我们@Override在配置器中使用了 of 方法,那么AuthenticationManagerBuilder将仅用于构建“本地” AuthenticationManager,这将是全局方法的子对象。在 Spring Boot 应用程序中,您可以@Autowired将全局 bean 放入另一个 bean,但您不能对本地 bean 执行此操作,除非您自己显式公开它。Spring Boot 提供了一个默认的全局AuthenticationManager(只有一个用户),除非您通过提供自己的 bean 类型来抢占它AuthenticationManager。默认值本身就足够安全,您不必担心太多,除非您主动需要自定义全局AuthenticationManager. 如果您进行任何构建AuthenticationManager.授权或访问控制一旦认证成功,我们就可以继续进行授权,这里的核心策略是AccessDecisionManager. 框架提供了三个实现,所有三个都委托给一个AccessDecisionVoter实例链,有点像ProviderManager委托给AuthenticationProviders.AnAccessDecisionVoter考虑一个Authentication(代表一个主体)和一个安全的Object,它被装饰了ConfigAttributes:boolean supports(ConfigAttribute attribute);
boolean supports(Class<?> clazz);
int vote(Authentication authentication, S object,
Collection<ConfigAttribute> attributes);在和的Object签名中是完全通用的。它代表用户可能想要访问的任何内容(Web 资源或 Java 类中的方法是最常见的两种情况)。它们也是相当通用的,代表安全的装饰,带有一些决定访问它所需的权限级别的元数据。是一个接口。它只有一个方法(非常通用并返回 a ),因此这些字符串以某种方式编码了资源所有者的意图,表达了关于允许谁访问它的规则。典型的是用户角色的名称(如or ),它们通常具有特殊格式(如AccessDecisionManagerAccessDecisionVoterConfigAttributesObjectConfigAttributeStringConfigAttributeROLE_ADMINROLE_AUDITROLE_前缀)或表示需要评估的表达式。大多数人使用默认值AccessDecisionManager,即AffirmativeBased(如果任何选民肯定返回,则授予访问权限)。通过添加新的或修改现有的工作方式,任何定制都倾向于发生在选民身上。使用 Spring 表达式语言 (SpEL) 表达式非常常见ConfigAttributes——例如,isFullyAuthenticated() && hasRole('user'). 这由AccessDecisionVoter可以处理表达式并为它们创建上下文的 an 支持。要扩展可以处理的表达式范围,SecurityExpressionRoot有时还需要自定义实现SecurityExpressionHandler.网络安全Web 层中的 Spring Security(用于 UI 和 HTTP 后端)是基于 Servlet 的Filters,所以首先看一下Filters一般的作用是有帮助的。下图显示了单个 HTTP 请求的处理程序的典型分层。客户端向应用程序发送请求,容器根据请求 URI 的路径决定应用哪些过滤器和哪个 servlet。最多一个 servlet 可以处理一个请求,但是过滤器形成一个链,所以它们是有序的。事实上,如果过滤器想要自己处理请求,它可以否决链的其余部分。过滤器还可以修改下游过滤器和 servlet 中使用的请求或响应。过滤器链的顺序非常重要,Spring Boot 通过两种机制来管理它:@Beans类型Filter可以有一个@Order或实现Ordered,它们可以是一个FilterRegistrationBean它本身有一个订单作为其 API 的一部分。一些现成的过滤器定义了自己的常量来帮助表明他们喜欢的相对于彼此的顺序(例如,SessionRepositoryFilter来自 Spring Session 有一个DEFAULT_ORDERof Integer.MIN_VALUE + 50,它告诉我们它喜欢在链中处于早期,但是它不排除在它之前出现其他过滤器)。Spring Security 在链中作为单个安装Filter,其具体类型是FilterChainProxy,原因我们很快就会介绍。在 Spring Boot 应用程序中,安全过滤器位于@Bean中ApplicationContext,默认情况下会安装它,以便将其应用于每个请求。它安装在由 定义的位置SecurityProperties.DEFAULT_FILTER_ORDER,该位置又被锚定FilterRegistrationBean.REQUEST_WRAPPER_FILTER_MAX_ORDER(Spring Boot 应用程序在包装请求时期望过滤器具有的最大顺序,修改其行为)。不仅如此:从容器的角度来看,Spring Security 是一个单一的过滤器,但在其中,还有额外的过滤器,每个过滤器都扮演着特殊的角色。下图显示了这种关系:图 2. Spring Security 是单一物理的Filter,但将处理委托给内部过滤器链实际上,安全过滤器中甚至还有一层间接性:它通常以 . 的形式安装在容器中DelegatingFilterProxy,而不必一定是 Spring @Bean。代理委托给 a FilterChainProxy,它始终是 a @Bean,通常具有固定名称springSecurityFilterChain。它FilterChainProxy包含在内部排列为过滤器链(或链)的所有安全逻辑。所有过滤器都具有相同的 API(它们都实现了FilterServlet 规范中的接口),并且它们都有机会否决链的其余部分。可以有多个过滤器链都由 Spring Security 在同一顶层管理,FilterChainProxy并且对容器都是未知的。Spring Security 过滤器包含一个过滤器链列表,并将请求分派到与其匹配的第一个链。下图显示了基于匹配请求路径(/foo/**匹配之前/**)发生的调度。这很常见,但不是匹配请求的唯一方法。这个分派过程最重要的特点是只有一个链处理一个请求。图 3. Spring SecurityFilterChainProxy将请求分派到匹配的第一个链。没有自定义安全配置的普通 Spring Boot 应用程序有几个(称为 n)过滤器链,其中通常 n=6。第一个 (n-1) 个链只是为了忽略静态资源模式,例如/css/**and/images/**和错误视图:/error. (路径可以由用户使用security.ignored配置SecurityPropertiesbean 控制。)最后一个链匹配包罗万象的路径 ( /**) 并且更加活跃,包含身份验证、授权、异常处理、会话处理、标头写入等逻辑上。默认情况下,该链中共有 11 个过滤器,但通常用户无需关心使用哪些过滤器以及何时使用。容器不知道 Spring Security 内部的所有过滤器这一事实很重要,尤其是在 Spring Boot 应用程序中,默认情况下,所有@Beans类型Filter都自动注册到容器中。因此,如果您想将自定义过滤器添加到安全链中,则需要不将其设为 a@Bean或将其包装在FilterRegistrationBean明确禁用容器注册的 a 中。创建和自定义过滤器链Spring Boot 应用程序(具有请求匹配器的应用程序)中的默认后备过滤器链/**具有预定义的SecurityProperties.BASIC_AUTH_ORDER. 您可以通过设置将其完全关闭security.basic.enabled=false,也可以将其用作后备并以较低的顺序定义其他规则。要执行后者,请添加一个(或)@Bean类型并用 装饰类,如下所示:WebSecurityConfigurerAdapterWebSecurityConfigurer@Order@Configuration
@Order(SecurityProperties.BASIC_AUTH_ORDER - 10)
public class ApplicationConfigurerAdapter extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http.antMatcher("/match1/**")
...;
}
}这个 bean 导致 Spring Security 添加一个新的过滤器链并在回退之前对其进行排序。与另一组相比,许多应用程序对一组资源的访问规则完全不同。例如,托管 UI 和支持 API 的应用程序可能支持基于 cookie 的身份验证,通过重定向到 UI 部分的登录页面和基于令牌的身份验证,以及对 API 部分的未经身份验证请求的 401 响应。每组资源都有自己WebSecurityConfigurerAdapter的唯一顺序和自己的请求匹配器。如果匹配规则重叠,则最早排序的过滤器链获胜。请求匹配调度和授权安全过滤器链(或等效的 aWebSecurityConfigurerAdapter)有一个请求匹配器,用于决定是否将其应用于 HTTP 请求。一旦决定应用特定的过滤器链,就不会应用其他过滤器链。但是,在过滤器链中,您可以通过在配置器中设置额外的匹配器来对授权进行更细粒度的控制HttpSecurity,如下所示:@Configuration
@Order(SecurityProperties.BASIC_AUTH_ORDER - 10)
public class ApplicationConfigurerAdapter extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http.antMatcher("/match1/**")
.authorizeRequests()
.antMatchers("/match1/user").hasRole("USER")
.antMatchers("/match1/spam").hasRole("SPAM")
.anyRequest().isAuthenticated();
}
}配置 Spring Security 时最容易犯的错误之一就是忘记这些匹配器适用于不同的进程。一种是整个过滤器链的请求匹配器,另一种只是选择要应用的访问规则。将应用程序安全规则与执行器规则相结合如果您将 Spring Boot Actuator 用于管理端点,您可能希望它们是安全的,并且默认情况下它们是安全的。事实上,只要将执行器添加到安全应用程序,您就会获得一个仅适用于执行器端点的附加过滤器链。它是使用仅匹配执行器端点的请求匹配器定义的,它的顺序为ManagementServerProperties.BASIC_AUTH_ORDER,比默认的SecurityProperties回退过滤器少 5 个,因此在回退之前对其进行查询。如果您希望您的应用程序安全规则应用于执行器端点,您可以添加一个比执行器更早排序的过滤器链,并且该过滤器链具有包含所有执行器端点的请求匹配器。如果您更喜欢执行器端点的默认安全设置,最简单的方法是在执行器之后添加您自己的过滤器,但在回退之前添加(例如,ManagementServerProperties.BASIC_AUTH_ORDER + 1),如下所示:@Configuration
@Order(ManagementServerProperties.BASIC_AUTH_ORDER + 1)
public class ApplicationConfigurerAdapter extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http.antMatcher("/foo/**")
...;
}
}Web 层中的 Spring Security 当前与 Servlet API 相关联,因此它仅在 servlet 容器中运行应用程序时才真正适用,无论是嵌入的还是其他的。但是,它不依赖于 Spring MVC 或 Spring Web 堆栈的其余部分,因此它可以在任何 servlet 应用程序中使用——例如,使用 JAX-RS 的应用程序。方法安全除了支持保护 Web 应用程序之外,Spring Security 还支持将访问规则应用于 Java 方法执行。对于 Spring Security,这只是一种不同类型的“受保护资源”。对于用户,这意味着使用相同格式的ConfigAttribute字符串(例如,角色或表达式)声明访问规则,但在代码中的不同位置。第一步是启用方法安全性——例如,在我们应用程序的顶层配置中:@SpringBootApplication
@EnableGlobalMethodSecurity(securedEnabled = true)
public class SampleSecureApplication {
}然后我们可以直接装饰方法资源:@Service
public class MyService {
@Secured("ROLE_USER")
public String secure() {
return "Hello Security";
}
}此示例是具有安全方法的服务。如果 Spring 创建了@Bean这种类型的 a,它会被代理,调用者必须在方法实际执行之前通过安全拦截器。如果访问被拒绝,调用者会得到一个AccessDeniedException而不是实际的方法结果。您可以在方法上使用其他注释来强制实施安全约束,特别是@PreAuthorize和@PostAuthorize,它们允许您编写分别包含对方法参数和返回值的引用的表达式。将 Web 安全性和方法安全性结合起来并不少见。过滤器链提供用户体验功能,例如身份验证和重定向到登录页面等,方法安全性提供更细粒度的保护。使用线程Spring Security 基本上是线程绑定的,因为它需要使当前经过身份验证的主体可用于各种下游消费者。基本构建块是SecurityContext,它可能包含一个Authentication(当用户登录时,它是一个Authentication显式的authenticated)。您始终可以SecurityContext通过静态便捷方法访问和SecurityContextHolder操作ThreadLocal. 以下示例显示了这种安排:SecurityContext context = SecurityContextHolder.getContext();
Authentication authentication = context.getAuthentication();
assert(authentication.isAuthenticated);用户应用程序代码执行此操作并不常见,但如果您需要编写自定义身份验证过滤器(尽管即使这样,Spring Security 中也有一些基类可供您使用,以便您可以避免需要使用SecurityContextHolder)。如果您需要访问 Web 端点中当前经过身份验证的用户,可以在 a 中使用方法参数@RequestMapping,如下所示:@RequestMapping("/foo")
public String foo(@AuthenticationPrincipal User user) {
... // do stuff with user
}此注解将电流Authentication拉出SecurityContext并调用其上的getPrincipal()方法以产生方法参数。Principalin an的类型Authentication取决于AuthenticationManager用于验证身份验证的类型,因此这可能是一个有用的小技巧,可以获取对用户数据的类型安全引用。如果使用 Spring Security,则PrincipalfromHttpServletRequest是 type Authentication,所以你也可以直接使用它:@RequestMapping("/foo")
public String foo(Principal principal) {
Authentication authentication = (Authentication) principal;
User = (User) authentication.getPrincipal();
... // do stuff with user
}如果您需要编写在不使用 Spring Security 时工作的代码,这有时会很有用(您需要在加载Authentication类时更加防御)。异步处理安全方法由于SecurityContext是线程绑定的,如果您想要执行任何调用安全方法的后台处理(例如, with @Async),您需要确保传播上下文。这归结为使用在后台执行SecurityContext的任务(Runnable、等)包装 。CallableSpring Security 提供了一些帮助程序来简化此操作,例如 和 的包装Runnable器Callable。要传播SecurityContextto@Async方法,您需要提供AsyncConfigurer并确保其Executor类型正确:@Configuration
public class ApplicationConfiguration extends AsyncConfigurerSupport {
@Override
public Executor getAsyncExecutor() {
return new DelegatingSecurityContextExecutorService(Executors.newFixedThreadPool(5));
}
}#spring##java##程序员##spring认证##Spring中国教育管理中心#文末备注:Spring Security Architecture来源:Spring中国教育管理中心
sqlite3——基本命令语句与应用
Linux下学习数据库1 安装数据库软件(DBMS)安装语句:SQLite3 sudo apt install sqlite3SQLite,是一款轻型的数据库,是遵守ACID的关系型数据库管理系统, 它包含在一个相对小的C库中。它是D.RichardHipp建立的公有领域项 目。它的设计目标是嵌入式的,而且已经在很多嵌入式产品中使用了 它,它占用资源非常的低,在嵌入式设备中,可能只需要几百K的内存就 够了。它能够支持Windows/Linux/Unix等等主流的操作系统,同时能够 跟很多程序语言相结合,比如 Tcl、C#、PHP、Java等,还有ODBC接 口,同样比起Mysql、PostgreSQL这两款开源的世界著名数据库管理系统 来讲,它的处理速度比他们都快。SQLite第一个Alpha版本诞生于2000年 5月。 至2020年已经有20个年头,SQLite也迎来了一个版本 SQLite 3已经 发布。2 SQLite3常用命令sqlite3 xxx.db.database.exit.help.show.schema tablename3 SQLite3常用数据类型NULL 值是一个NULL值INTEGER 值是一个带符号的整数, 根据值的大小存储在1, 2, 3, 4, 6或8字 节中REAL 值是一个浮点值, 存储为8字节的IEEE浮点数字TEXT 值是一个文本字符串, 使用数据编码(UTF-8, YTF-16B或UTF-16LE) 存储4 SQLite3的SQL语句 创建数据库 sqlite3 testDB.db
创建表 create table tablename( column1 datatype, column2 datatype, column3 datatype);
删除表 drop table tablename;
添加数据到表 insert into tablename [(colnum1, colnum2, colnum3, colnum4.......)] values(value1, value2, value3, value4.......); insert into tablename values(value1, value2, value3, value4.......);
查询表数据 select * from tablename;
where表达式 select * from tablename where colnum1 = value1;
更新表数据 update tablename set colnum1 = value1 where colnum2 = value2
删除表数据 delete from tablename where colnum1 = value1;
like表达式 select * from where colnum1 like '%_'
5.案例注意:gcc main.c之后要加上 -lsqlite3(-l参数就是用来指定程序要链接的库,-l参数紧接着就是库名)运行a.out时候后面要加上传入的数据库名称/*************************************************************************
> File Name: main.c
> Author: 杨永利
> Mail: 1795018360@qq.com
> Created Time: 2020年07月27日 星期一 22时13分59秒
************************************************************************/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sqlite3.h>
int main(int argc, char* argv[]){
// 判断是否有两个参数,否则不知道打开哪个数据库
if(argc != 2)
exit(1);
//设置一个数据库指针为空
sqlite3 *db=NULL;
// 设置错误信息提示指针
char *errorstr;
// 判断打开这个数据库是否成功
if(sqlite3_open(argv[1],&db))
{
//如果失败就提示错误信息并关闭数据库推出程序
printf("sqlite3_open() 函数打开数据库失败!\n");
sqlite3_close(db);
exit(1);
}
else{
//如果打开成功则提示该数据库下所有的表
printf("该数据库下所有的表:\n");
// 数据库查询语句
char sql[100] = "select * from sqlite_master;";
//接收数据返回值的char**类型
char** buf;
//接收表的列数和行数
int row,col;
//从表中获取数据函数 sqlite3_get_table()
//第一个参数为数据库指针
//第二个参数为sql语句
//第三个参数用来接收查询语句的数据返回值
//第四个参数用来接收该数据有多少行
//第五个参数用来接收该数据有多少列
//第六个参数用来接收错误信息
if(sqlite3_get_table(db, sql, &buf, &row, &col, &errorstr))
{
//如果函数调用失败则显示错误信息并关闭数据库退出程序
printf("sqlite3_get_table()函数获取表失败!\n");
sqlite3_close(db);
exit(1);
}
// 开启for循环用于显示所有数据表
int i=0;
int now_col=0; //检索到的当前行
for(i;i<(row+1)*col;i++) //i<(row+1)*col 是
{
printf("%s",buf[i]);
// 当前列不是倒数第一列就输出 |
if(now_col!=col-1){
printf("|");
}
// 如果是就输出换行,并且初始化为0
if(now_col==col-1){
now_col=0;
printf("\n");
}else // 否则就 列数+1 换成下一列
now_col++;
}
}
// 如果执行到这一步就说明用户已经看到了数据表
// 开启while循环让用户去查询每个表的数据
while(1)
{
// 询问用户所要查询的表
printf("\n请输入您想要查询的表......\n输入Q(q)来退出程序\n\n");
// 定义一个char数组来接受表明
char tablename[100];
// 定义好查询语句
char sql[100]="select *from ";
// 获取用户输入的表面
fgets(tablename,sizeof(tablename),stdin);
// 判断是否退出程序
if(!strcmp(tablename, "q\n") || !strcmp(tablename, "Q\n"))
exit(1);
strcat(sql,tablename); // 拼接字符串
//设置表名最后一个字符为零
tablename[strlen(tablename)-1]='\0';
// 接受数据的返回值 char**类型
char **buf;
//接收表的列数和行数
int row, col;
if(sqlite3_get_table(db, sql, &buf, &row, &col, &errorstr)){
//如果函数调用失败则显示错误信息并关闭数据库退出程序
printf("sqlite3_get_table()函数获取表失败!\n");
sqlite3_close(db);
exit(1);
}
//开启for循环显示该表的数据
int i = 0;
int now_col = 0;
if(row*col==0)
{
printf("\n表%s中无数据\n",tablename);
continue;
}
// 有数据就执行下面语句显示所有数据
printf("\n表 %s 下所有的数据:\n", tablename);
for(i = col; i < (row + 1) * col; i++){
printf("%s", buf[i]);
if(now_col != col - 1)
printf("|");
if(now_col == col - 1){
now_col = 0;
printf("\n");
}else
now_col++;
}
}
return 0;
}
最佳实践—如何优化Batch Insert
INSERT [IGNORE] [INTO] table_name(column_name, ...) VALUES (value1, ...), (value2, ...), ...;影响Batch Insert性能的主要因素包括:batch size并行度分片数目列数目GSI的数目sequence数目对于分片数目、列数目、GSI数目、sequence数目等内需因素,根据实际需求进行设置,并且常常会和读性能相互影响,例如GSI数目较多情况下,写入性能肯定会下降,但是对读性能有提升。本文不详细讨论这些因素的影响,主要聚焦于batch size和并行度的合理设置。测试环境本文档的测试环境见下表:环境参数PolarDB-X版本polarx-kernel_5.4.11-16279028_xcluster-20210802节点规格16核64GB节点个数4测试的表用例:CREATE TABLE `sbtest1` ( `id` int(11) NOT NULL, `k` int(11) NOT NULL DEFAULT '0', `c` char(120) NOT NULL DEFAULT '', `pad` char(60) NOT NULL DEFAULT '', PRIMARY KEY (`id`), KEY `k_1` (`k`)) ENGINE = InnoDB DEFAULT CHARSET = utf8mb4;Batch特性:BATCH_INSERT_POLICY=SPLITPolarDB-X针对数据批量写入,为保障更好的并发性,对Batch Insert进行了优化,当单个Batch Insert语句大小超过256K时,PolarDB-X会将Batch Insert语句动态拆分成多个小Batch,多个小Batch之间串行执行,这个特性称为SPLIT。通过BATCH_INSERT_POLICY=SPLIT的机制,在保障最佳性能的同时,减少PolarDB-X并行执行Batch Insert的代价,尽可能规避分布式下多节点的负载不均衡。相关参数:BATCH_INSERT_POLICY,可选SPLIT/NONE,默认值为SPLIT,代表默认启用动态拆分Batch。MAX_BATCH_INSERT_SQL_LENGTH,默认值256,单位KB。代表触发动态拆分Batch的SQL长度阈值为256K。BATCH_INSERT_CHUNK_SIZE_DEFAULT,默认值200。代表触发动态拆分Batch时,每个拆分之后的小Batch的批次大小。关闭BATCH_INSERT_POLICY=SPLIT机制,可通过如下hint语句/+TDDL:CMD_EXTRA(BATCH_INSERT_POLICY=NONE)/ 。 此参数的目标是关闭BATCH_INSERT_POLICY策略,这样才可以保证batch size在PolarDB-X执行时不做自动拆分,可用于验证batch size为2000、5000、10000下的性能,从测试的结果来看batch size超过1000以后提升并不明显。单表的性能基准在分布式场景下单表只会在一个主机上,其性能可以作为一个基础的性能基线,用于评测分区表的水平扩展的能力,分区表会将数据均匀分布到多台主机上。测试方法为对PolarDB-X中的单表进行Batch Insert操作,单表的数据只会存在一个数据存储节点中,PolarDB-X会根据表定义将数据写入到对应的数据存储节点上。场景一:batch size参数配置:并行度:16列:4gsi:无sequence:无测试项batch size11010050010002000500010000PolarDB-X【单表】性能(行每秒)539745653153216211976210644215103221919220529场景二:并行度参数配置:batch size:1000列:4gsi:无sequence:无测试项thread1248163264128PolarDB-X【单表】性能(行每秒)226254132676052127646210644223431190138160858测试总结对于单表的测试,推荐batch size为1000,并行度为16~32时整体性能比较好。在测试batch size为2000、5000、10000时,需要添加hint参数来关闭SPLIT特性,从测试的结果来看batch size超过1000以后提升并不明显。示例:/+TDDL:CMD_EXTRA(BATCH_INSERT_POLICY=NONE)/分区表的性能基准Batch size和并行度都会影响Batch Insert的性能,下面对这两个因素分开进行测试分析。场景一:batch Size在数据分片的情况下,由于包含拆分函数,Batch Insert语句会经过拆分函数分离values,下推到物理存储上的batch size会改变,示意图如下图所示。INSERT [IGNORE] [INTO] table_name(column_name, ...) VALUES (value1, ...), (value2, ...), ...;影响Batch Insert性能的主要因素包括:batch size并行度分片数目列数目GSI的数目sequence数目对于分片数目、列数目、GSI数目、sequence数目等内需因素,根据实际需求进行设置,并且常常会和读性能相互影响,例如GSI数目较多情况下,写入性能肯定会下降,但是对读性能有提升。本文不详细讨论这些因素的影响,主要聚焦于batch size和并行度的合理设置。测试环境本文档的测试环境见下表:环境参数PolarDB-X版本polarx-kernel_5.4.11-16279028_xcluster-20210802节点规格16核64GB节点个数4测试的表用例:CREATE TABLE `sbtest1` ( `id` int(11) NOT NULL, `k` int(11) NOT NULL DEFAULT '0', `c` char(120) NOT NULL DEFAULT '', `pad` char(60) NOT NULL DEFAULT '', PRIMARY KEY (`id`), KEY `k_1` (`k`)) ENGINE = InnoDB DEFAULT CHARSET = utf8mb4;Batch特性:BATCH_INSERT_POLICY=SPLITPolarDB-X针对数据批量写入,为保障更好的并发性,对Batch Insert进行了优化,当单个Batch Insert语句大小超过256K时,PolarDB-X会将Batch Insert语句动态拆分成多个小Batch,多个小Batch之间串行执行,这个特性称为SPLIT。通过BATCH_INSERT_POLICY=SPLIT的机制,在保障最佳性能的同时,减少PolarDB-X并行执行Batch Insert的代价,尽可能规避分布式下多节点的负载不均衡。相关参数:BATCH_INSERT_POLICY,可选SPLIT/NONE,默认值为SPLIT,代表默认启用动态拆分Batch。MAX_BATCH_INSERT_SQL_LENGTH,默认值256,单位KB。代表触发动态拆分Batch的SQL长度阈值为256K。BATCH_INSERT_CHUNK_SIZE_DEFAULT,默认值200。代表触发动态拆分Batch时,每个拆分之后的小Batch的批次大小。关闭BATCH_INSERT_POLICY=SPLIT机制,可通过如下hint语句/+TDDL:CMD_EXTRA(BATCH_INSERT_POLICY=NONE)/ 。 此参数的目标是关闭BATCH_INSERT_POLICY策略,这样才可以保证batch size在PolarDB-X执行时不做自动拆分,可用于验证batch size为2000、5000、10000下的性能,从测试的结果来看batch size超过1000以后提升并不明显。单表的性能基准在分布式场景下单表只会在一个主机上,其性能可以作为一个基础的性能基线,用于评测分区表的水平扩展的能力,分区表会将数据均匀分布到多台主机上。测试方法为对PolarDB-X中的单表进行Batch Insert操作,单表的数据只会存在一个数据存储节点中,PolarDB-X会根据表定义将数据写入到对应的数据存储节点上。场景一:batch size参数配置:并行度:16列:4gsi:无sequence:无测试项batch size11010050010002000500010000PolarDB-X【单表】性能(行每秒)539745653153216211976210644215103221919220529场景二:并行度参数配置:batch size:1000列:4gsi:无sequence:无测试项thread1248163264128PolarDB-X【单表】性能(行每秒)226254132676052127646210644223431190138160858测试总结对于单表的测试,推荐batch size为1000,并行度为16~32时整体性能比较好。在测试batch size为2000、5000、10000时,需要添加hint参数来关闭SPLIT特性,从测试的结果来看batch size超过1000以后提升并不明显。示例:/+TDDL:CMD_EXTRA(BATCH_INSERT_POLICY=NONE)/分区表的性能基准Batch size和并行度都会影响Batch Insert的性能,下面对这两个因素分开进行测试分析。场景一:batch Size在数据分片的情况下,由于包含拆分函数,Batch Insert语句会经过拆分函数分离values,下推到物理存储上的batch size会改变,示意图如下图所示。