ElasticSearch使用思路
Elasticsearch 可以对数据进行增删改查;字段(mapping)可以新增,但不能删除或修改类型;字段的值是可以修改的。
往MySQL添加完数据后,提交事物,接着往ElasticSearch里添加。
原则:搜索条件用得到、过滤条件用得到、列表页要展示。满足其中一条才往ES放。不满足的字段留在MySQL,详情页用id回查就行。
根据修改时间字段(WHERE update_time > 上次同步时间),使用定时任务,增量同步MySQL数据到ES。
定时任务
小项目
- 直接用
@Scheduled+ Redis锁
中大型项目
- 用 XXL-JOB 或 Quartz
- 不建议自己手写复杂调度
@Configuration
Spring 的配置类标记注解,用来定义 Bean,并让 Spring 容器管理这些 Bean。
要配合@Bean使用,@Bean→ 告诉 Spring 这个方法的返回值要被管理为 Bean
如果没有@Bean
Spring 扫描它后,会把 配置类本身注册为一个 Bean
结论:不会报错,Spring 可以正常启动,只是这个配置类本身没啥用
可以开启某些功能注解
java
//这里没有 @Bean,但 @EnableAspectJAutoProxy 会生效
@Configuration
@EnableAspectJAutoProxy(exposeProxy = true)
public class AopConfig {}
//在启动类上加上该注解也行。
//"@EnableAspectJAutoProxy(exposeProxy = true) 类似于向 Spring 容器注册了一个特殊的 Bean(处理器),只要配置类被扫描,这个功能就会生效,写在哪个配置类上都可以生效。"
当你需要给 Bean 自定义构造方法,或者在创建 Bean 时自己控制构造参数时,就必须用 @Configuration + @Bean,@Component 无法实现。
@Transactional
这是事务注解,用来控制数据库事务。
指定回滚异常
@Transactional(rollbackFor = Exception.class)
默认只回滚 RuntimeException
加(rollbackFor = Exception.class), 所有异常都回滚
传播行为
@Transactional(propagation = Propagation.REQUIRED)
REQUIRED(默认):有事务就加入,没有就创建
REQUIRES_NEW:新开一个事务
也就是事物里面又有事物时,两个事物是一起提交,一起回滚,还是互不影响。
失效
java
//1、自调用失效
this.method(); // 事务不生效,因为 Spring 用的是 AOP 代理
//通过 AOP 代理获取当前 Bean,使事物生效
((MyService) AopContext.currentProxy()).methodA(); //事务生效
//要求在配置类中开启 exposeProxy = true,见上节。
//2、不是 public 方法也失效
private void method() {} // 不生效
//3、try-catch 吞异常,吞了就不回滚
try {
...
} catch (Exception e) {
// 不抛出 → 不回滚
}
//解决办法
//方式1:继续抛异常(最推荐)
catch (Exception e) {
throw e; // ✅
}
//方式2:手动标记回滚
catch (Exception e) {
TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
}
//方式3:抛运行时异常(常见)
catch (Exception e) {
throw new RuntimeException(e);
}
存储过程
在 MySQL 里:存储过程(Stored Procedure)就是:把一段 SQL 逻辑写好,保存在数据库中,像"函数"一样调用。
Seata失效
1、99% @Transactional失效的情况,@GlobalTransactional 会失效
2、复杂 SQL 会导致 Seata 失效(或不安全)(JOIN UPDATE、 子查询 UPDATE、存储过程、批量复杂操作)
AQS
在 Java 里:AQS(AbstractQueuedSynchronizer)是一个用来构建锁和同步器的"基础框架"。
中文一般叫:抽象队列同步器
既是一种思想,也是一个框架,但更准确说: 是一个"基于队列的同步框架"
public abstract class AbstractQueuedSynchronizer
核心思想
用 state 表示"资源状态",0:资源可用,其他表示加锁多少次。用 FIFO 队列管理等待线程,先进先出(FIFO)。 CAS(抢资源),阻塞线程:LockSupport.park(); 唤醒线程:LockSupport.unpark(thread);
AQS 的公平性,不取决于"是否进入队列",而取决于"是否允许新线程插队"。
LockSupport
LockSupport 是线程阻塞与唤醒的"原子级工具",AQS 等高级同步器正是基于它构建的。
LockSupport.unpark(thread); // 发放许可
LockSupport.park(); // 消耗许可
LockSupport 的许可是"单次许可",不是计数许可:要么有(1),要么没有(0)。
java
import java.util.concurrent.locks.LockSupport;
public class Demo1 {
public static void main(String[] args) {
Thread t = new Thread(() -> {
System.out.println("线程开始执行");
LockSupport.park(); // 阻塞在这里
System.out.println("线程被唤醒");
});
t.start();
try {
Thread.sleep(2000);
} catch (InterruptedException e) {}
System.out.println("主线程唤醒子线程");
LockSupport.unpark(t); // 唤醒线程
}
}
线程轮流执行
方式1:wait / notify
一个对象用来加锁,一个boolean类型的flag,两个线程里两个循环,获取锁,然后判断flag,不是就wait,是就执行,然后修改flag,再notifyAll。
java
class AlternatingPrint {
private final Object lock = new Object();
private boolean flag = true;
public void print1() {
for (int i = 0; i < 5; i++) {
synchronized (lock) {
//如果用if,唤醒后,就往下执行了,可能存在伪唤醒
//while需要跳出循环再往下执行,所以必须用while
while (!flag) {
try {
lock.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("线程1:" + i);
flag = false;
lock.notifyAll();
}
}
}
public void print2() {
for (int i = 0; i < 5; i++) {
synchronized (lock) {
while (flag) {
try {
lock.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("线程2:" + i);
flag = true;
lock.notifyAll();
}
}
}
public static void main(String[] args) {
AlternatingPrint demo = new AlternatingPrint();
new Thread(demo::print1).start();
new Thread(demo::print2).start();
}
}
方式2:ReentrantLock + Condition
java
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;
class AlternatingPrint {
private ReentrantLock lock = new ReentrantLock();
//Condition 是 ReentrantLock 提供的"等待/唤醒工具",可以创建多个等待队列,比 wait/notify 更灵活
private Condition c1 = lock.newCondition();
private Condition c2 = lock.newCondition();
private boolean flag = true;
public void print1() {
for (int i = 0; i < 5; i++) {
lock.lock();
try {
while (!flag) {
c1.await();
}
System.out.println("线程1:" + i);
flag = false;
c2.signal();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
}
public void print2() {
for (int i = 0; i < 5; i++) {
lock.lock();
try {
while (flag) {
c2.await();
}
System.out.println("线程2:" + i);
flag = true;
c1.signal();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
}
public static void main(String[] args) {
AlternatingPrint demo = new AlternatingPrint();
new Thread(demo::print1).start();
new Thread(demo::print2).start();
}
}
方式3:LockSupport
java
import java.util.concurrent.locks.LockSupport;
class Demo {
static Thread t1, t2;
public static void main(String[] args) {
t1 = new Thread(() -> {
for (int i = 0; i < 5; i++) {
System.out.println("线程1:" + i);
LockSupport.unpark(t2);
LockSupport.park();
}
});
t2 = new Thread(() -> {
for (int i = 0; i < 5; i++) {
LockSupport.park();
System.out.println("线程2:" + i);
LockSupport.unpark(t1);
}
});
t1.start();
t2.start();
}
}