bboyjing's blog

Neo4j学习笔记十一【事物】

之前关于事物一章,由于当时的环境不方便测试所以跳过去了,现在回过头来,这一章一定要了解下,因为Neo4j是支持ACID的,示例项目采用上一章建的项目neo4j_sample下的springboot_sdn_embedded,使用Neo4j核心API演示。

事物声明方式

在没有引入Spring之前,事物采用如下原始的方式声明,引入Spring之后只需要@Transactional,这个相信大家都懂。

1
2
3
4
5
6
...
try(Transaction tx = graphDB.beginTx()){
...
tx.success();
}
...

另外有一点要注意下,如果在Neo4j中做任意与模式相关的操作(创建模式索引),需要在一个单独的事物中,否则会抛出异常。

事物隔离级别

下面先代码测试下看看Neo4j默认的事物隔离级别是什么样的

1
2
3
#给john节点添加age属性
start john = node(143)
set john.age = 30;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
//为了方便同时调试两个线程,项目引入spring-boot-starter-web,直接把父项目的spring-boot-starter改掉即可
@RestController
public class TransactionController {
@Autowired
private TransactionService transactionService;
@RequestMapping(path = "/threadA", method = RequestMethod.GET)
public String treadA(){
transactionService.threadA();
return "ok";
}
@RequestMapping(path = "/threadB", method = RequestMethod.GET)
public String treadB(){
transactionService.threadB();
return "ok";
}
}
@Service
public class TransactionService {
@Autowired
private GraphDatabaseService graphDatabaseService;
@Transactional
public void threadA(){
//1
Node john = graphDatabaseService.getNodeById(143l);
System.out.println(john.getProperty("age"));
//4
john = graphDatabaseService.getNodeById(143l);
System.out.println(john.getProperty("age"));
//6
john = graphDatabaseService.getNodeById(143l);
System.out.println(john.getProperty("age"));
}
@Transactional
public void threadB(){
//2
Node john = graphDatabaseService.getNodeById(143l);
System.out.println(john.getProperty("age"));
//3
john.setProperty("age", 35);
System.out.println(john.getProperty("age"));
//5、threadB方法结束,提交事物
}
}
//主要测试代码如上,但是这样会有个问题,启动项目的时候会首先初始化Ne4jConfig中的GraphDatabaseService,
//此时SessionFactory还没加载会得到一个空的driver,导致项目启动失败。通过如下办法可以解决,
//即在graphDatabaseService()方法中主动调用getSessionFactory(),将SessionFactory先初始化好
@Bean
public GraphDatabaseService graphDatabaseService(){
getSessionFactory();
EmbeddedDriver embeddedDriver = (EmbeddedDriver) Components.driver();
return embeddedDriver.getGraphDatabaseService();
}

IDEA中同时调试两个线程

随便选择一个红色断点右击,会弹出一个对话框,选择将All改成Thread,并设置成默认,这样调试的时候,在Debugger标签页就能选择对应的线程debug。
图14
浏览器开两个窗口先后请求localhost:8080/threadA和localhost:8080/threadB,首先断点都停在了controller,然后按照顺序选择对应的线程debug。我debug的顺序在TransactionService注释中已经标明,输出结果依次为30、30、35、30、35,所以最终可以得出结论,threadA可以读到threadB已经提交过的修改,也就是说和传统关系型数据库的READ COMMITTED隔离级别相似。

更高级别的锁控制

Neo4j的事物是由清晰的读锁和写锁来控制每一个图形数据库资源的,下面我们看下如何手动控制锁

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
public void readLock(){
try(Transaction tx= graphDatabaseService.beginTx()){
//1
Node john = graphDatabaseService.getNodeById(143l);
System.out.println(john.getProperty("age"));
//读锁,其他线程对于该节点的写会等待读锁释放
tx.acquireReadLock(john);
//4
john = graphDatabaseService.getNodeById(143l);
System.out.println(john.getProperty("age"));
tx.success();
}
}
@Transactional
public void writeWaitReadLock(){
//2
Node john = graphDatabaseService.getNodeById(143l);
System.out.println(john.getProperty("age"));
//3
john.setProperty("age", 38);
System.out.println(john.getProperty("age"));
}
@RequestMapping(path = "/readLock", method = RequestMethod.GET)
public String readLock(){
transactionService.readLock();
return "ok";
}
@RequestMapping(path = "/writeWaitReadLock", method = RequestMethod.GET)
public String writeWaitReadLock(){
transactionService.writeWaitReadLock();
return "ok";
}

跟之前一样的方式调试下,在readLock()方法中通过tx.acquireReadLock(john),获取了john节点的读锁,然后再跑writeWaitReadLock(),当跑到步骤3的setProperty方法时会进入等待,此时再跑完readLock()线程,事物提交了,读锁也自动释放了,writeWaitReadLock()线程会立刻被激活,然后顺利完成自己的事物。输出结果依次为35、35、35、38,正常。