记录遇到的需求或问题

这篇博客记录了在JPA+Mybatis项目开发中遇到的一些问题,包括ApplicationContextAware与@PostConstruct冲突、在Hibernate的nullSafeSet方法中获取表名、JPA自增策略使用雪花算法导致的异常、OceanBase数据库的外键约束异常以及Bucket4j实现同主键限流的实践。

记录遇到的需求或问题



前言

项目结构是JPA+Mybatis+Jsp+数据库不固定。记录遇到的问题方便查找。


一、ApplicationContextAware与@PostConstruct冲突

2023-07-17
启动项目时调用@PostConstruct声明的方法,方法中使用了SpringContextUtil工具类,工具类中的applicationContext出现空指针。

解决:在@PostConstruct声明的类上添加注解@DependsOn(“springContextUtil”),如果套用多层Service放在了最下层,AService-》BService-》CService,注解放在了CService上。SpringContextUtil类上添加@Component,例如:
SpringContextUtil:
在这里插入图片描述
Service:
在这里插入图片描述

二、在nullSafeSet方法中获取到当前执行SQL的表名或类名

2023-07-18

方案一:
直接调用PreparedStatement的toString方法,可以得到SQL,截取一下就能获取表名。但是根据本人查到的资料这个与用到的数据库驱动有关,Oracle可能并不适用;

	String tableName = null;
        //MySQL与Postgresql可行,Oracle可能不行
        String sql = null;
        PreparedStatement delegatePreparedStatement = getDelegatePreparedStatement(ps);
        sql = delegatePreparedStatement.toString();
        if (sql.contains("insert into ")) {
            int tableNameStartIndex = sql.indexOf("insert into ") + "insert into ".length();
            int tableNameEndIndex = sql.indexOf("(", tableNameStartIndex);
            tableName = sql.substring(tableNameStartIndex, tableNameEndIndex).trim();
        } else if (sql.contains("update")) {
            int tableNameStartIndex = sql.indexOf("update") + "update".length();
            int tableNameEndIndex = sql.indexOf("set", tableNameStartIndex);
            tableName = sql.substring(tableNameStartIndex, tableNameEndIndex).trim();
        }
     /**
     * 尝试获取具体代理实现
     *
     * @param preparedStatement ps
     * @return delegate
     */
    public PreparedStatement getDelegatePreparedStatement(PreparedStatement preparedStatement) {

        if (preparedStatement instanceof ProxyStatement) {
            try {
                Field delegateField = ProxyStatement.class.getDeclaredField("delegate");
                delegateField.setAccessible(true);
                return (PreparedStatement) delegateField.get(preparedStatement);
            } catch (NoSuchFieldException | IllegalAccessException e) {
                // 处理异常
                logger.warn("Failed to obtain specific proxy implementation", e);
            }
        }
        return preparedStatement;
    }

方案二:
JPA模式下可以从SharedSessionContractImplementor中拿到对应的EntityEntry,从中取到表名,但是取到的有多个,可能是事务原因,同一个事务中有三个新增取到的就有3个。并没有深入研究,仅猜测,只是一种可能性。

        PersistenceContext persistenceContext = ssci.getPersistenceContext();
        Map.Entry<Object, EntityEntry>[] entries = persistenceContext.reentrantSafeEntityEntries();
        List<String> tableList = new ArrayList<>();
        for (Map.Entry<Object, EntityEntry> entry : entries) {
            EntityPersister persister = entry.getValue().getPersister();

            //获取表名
            Serializable[] querySpaces = persister.getQuerySpaces();
            if (querySpaces.length != 0) {
                tableList.add(querySpaces[0].toString());
            }
        }

三、JPA自增策略使用雪花算法并且在nullSafeSet方法存在持久化操作出现异常

2023-07-18
出现两种异常:ConcurrentModificationException和StackOverflowError,具体原因不明,只知道是事务问题。

解决:
nullSafeSet中的持久化操作改为手动提交事务。

		@Autowired
    	private PlatformTransactionManager platformTransactionManager;

		DefaultTransactionDefinition transactionDefinition=new DefaultTransactionDefinition();
        transactionDefinition.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRES_NEW);
        TransactionStatus status = platformTransactionManager.getTransaction(transactionDefinition);
        try {
            XXXDao.saveAll(xxxList);
            platformTransactionManager.commit(status);
        } catch (Exception e) {
            System.out.println("==========================事务回滚==============================");
            System.out.println(e.getMessage());
            platformTransactionManager.rollback(status);
            throw e;
        }

其实最好的方案是别在nullSafeSet中持久化。

四、OceanBase社区版4.1出现Cannot delete or update a parent row: a foreign key constraint fails

使用JPA+OceanBase删除存在关联外键的表是出现Cannot delete or update a parent row: a foreign key constraint fails异常
解决:
到社区论坛问了开发人员,是个bug,升版和降版都可以解决,例如4.0.0.0
在这里插入图片描述

五、Bucket4j对同主键id进行限流

需求:遇到了DDOS攻击,使用同主键的数据进行了几十万次访问,然后就有了这个需求。
1、添加依赖

<!-- bucket4j 限流框架 -->
<dependency>
   <groupId>com.github.vladimir-bukhtoyarov</groupId>
   <artifactId>bucket4j-core</artifactId>
   <version>7.6.0</version>
</dependency>

2、限流service
因为程序中已经使用了net.sf.ehcache,所以这里直接用的它来当缓存。

@Service
public class RateLimiterService {

    @Autowired
    private CacheManager cacheManager;

    public ConsumptionProbe setBucketAndConsume(String key) {
        //从cacheManager中获取缓存
        Cache buckets = cacheManager.getCache("buckets");
        Element element = buckets.get(key);
        Bucket bucket;
        if (element == null) {
            LocalBucketBuilder builder = Bucket.builder();
            /*  Bandwidth 表示令牌桶的容量和填充速率的组合
                Refill.intervally 方法用于创建一个填充策略,它的参数表示两次填充之间的时间间隔
             */
            builder.addLimit(Bandwidth.classic(5, Refill.intervally(5, Duration.ofMinutes(1))));
            bucket = builder.build();
            Element cachedElement = new Element(key, bucket);
            buckets.put(cachedElement);
        } else {
            bucket = (Bucket) element.getObjectValue();
        }
        //消耗令牌
        return bucket.tryConsumeAndReturnRemaining(1);
    }
}

3、chcache.xml中添加cache

<!-- bucket param setting-->
<cache name="buckets"
       maxEntriesLocalHeap = "10000"
       timeToLiveSeconds = "3600">
</cache>

4、到需要限流的地方引入使用

@Autowired
private RateLimiterService rateLimiterService;
//同ID限流
ConsumptionProbe probe = rateLimiterService.setBucketAndConsume(id);
// 无法消耗,直接抛出限流异常
if (!probe.isConsumed()) {
    throw new ErrorException();
}
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值