记录遇到的需求或问题
文章目录
前言
项目结构是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();
}
这篇博客记录了在JPA+Mybatis项目开发中遇到的一些问题,包括ApplicationContextAware与@PostConstruct冲突、在Hibernate的nullSafeSet方法中获取表名、JPA自增策略使用雪花算法导致的异常、OceanBase数据库的外键约束异常以及Bucket4j实现同主键限流的实践。

1770

被折叠的 条评论
为什么被折叠?



