title: Linux mv 操作是否新创建空间
date: 2017-02-24 17:36:53

tags:编程开发

Linux mv 操作的探讨

前期内容

Linux 操作过程中,可以通过 ll -i 查看inode

inode详解
linux上的inode编号是索引节点的编号。理解inode,要从文件储存说起。
  文件储存在硬盘上,硬盘的最小存储单位叫做”扇区”(Sector)。每个扇区储存512字节(相当于0.5KB)。
  操作系统读取硬盘的时候,不会一个个扇区地读取,这样效率太低,而是一次性连续读取多个扇区,即一次性读取一个”块”(block)。这种由多个扇区组成的”块”,是文件存取的最小单位。”块”的大小,最常见的是4KB,即连续八个 sector组成一个 block。
  文件数据都储存在”块”中,那么很显然,还必须找到一个地方储存文件的元信息,比如文件的创建者、文件的创建日期、文件的大小等等。这种储存文件元信息的区域就叫做inode,中文译名为”索引节点”。
  每个inode都有一个号码,操作系统用inode号码来识别不同的文件。这里值得重复一遍,Unix/Linux系统内部不使用文件名,而使用inode号码来识别文件。对于系统来说,文件名只是inode号码便于识别的别称或者绰号。表面上,用户通过文件名,打开文件。实际上,系统内部这个过程分成三步:首先,系统找到这个文件名对应的inode号码;其次,通过inode号码,获取inode信息;最后,根据inode信息,找到文件数据所在的block,读出数据。

结论

mv 在操作过程中不会改变文件在磁盘上的位置。

测试

1 查看当前Linux中磁盘情况

1
df

显示如图:
1

2 查看需要mv的文件的inode

1
ll -i

显示如图:
2

3 移动并查看移动后的文件的inode
显示如图
3

JAVA Integer

Java Integer小实验

背景

在项目过程中,有同事在Integer之间使用的==进行比较,导致现网上面一直无法匹配,但是在测试过程中并没有发现类似的问题,所以有必要对Integer之间的比较进行一个调研。

实验

在比较过程中我书写了如下代码进行比较:

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
public class Main {

public static void main(String[] args) {
Integer integer127 = 127;
Integer integer128 = 128;
Integer newInteger127 = new Integer(127);
int int127 = 127;
int int128 = 128;
Integer integer128copy = 128;
Integer integer127copy = 127;
boolean compareA = integer127 == newInteger127;
boolean compareB = integer127 == int127;
boolean compareC = integer128 == int128;
boolean compareD = integer128 == integer128copy;
boolean compareE = integer127 == integer127copy;
System.out.println("integer127 == newInteger127: " + compareA);
System.out.println("integer127 == int127: " + compareB);
System.out.println("integer128 == int128: " + compareC);
System.out.println("integer128 == integer128copy: " + compareD);
System.out.println("integer127 == integer127copy: " + compareE);
}
}
```
输出结果如下:
```JAVA
integer127 == newInteger127: false
integer127 == int127: true
integer128 == int128: true
integer128 == integer128copy: false
integer127 == integer127copy: true

分析

Compile代码

首先使用javac -p Main.claas查看其编译过程。部分内容截图如下:

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
64
65
66
67
public class Main {
public Main();
Code:
0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>":()V
4: return

public static void main(java.lang.String[]);
Code:
0: bipush 127
2: invokestatic #2 // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
5: astore_1
6: sipush 128
9: invokestatic #2 // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
12: astore_2
13: new #3 // class java/lang/Integer
16: dup
17: bipush 127
19: invokespecial #4 // Method java/lang/Integer."<init>":(I)V
22: astore_3
23: bipush 127
25: istore 4
27: sipush 128
30: istore 5
32: sipush 128
35: invokestatic #2 // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
38: astore 6
40: bipush 127
42: invokestatic #2 // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
45: astore 7
47: aload_1
48: aload_3
49: if_acmpne 56
52: iconst_1
53: goto 57
56: iconst_0
57: istore 8
59: aload_1
60: invokevirtual #5 // Method java/lang/Integer.intValue:()I
63: iload 4
65: if_icmpne 72
68: iconst_1
69: goto 73
72: iconst_0
73: istore 9
75: aload_2
76: invokevirtual #5 // Method java/lang/Integer.intValue:()I
79: iload 5
81: if_icmpne 88
84: iconst_1
85: goto 89
88: iconst_0
89: istore 10
91: aload_2
92: aload 6
94: if_acmpne 101
97: iconst_1
98: goto 102
101: iconst_0
102: istore 11
104: aload_1
105: aload 7
107: if_acmpne 114
110: iconst_1
111: goto 115
114: iconst_0
115: istore 12

查看编译初始化情况:

1
2
Integer integer127 = 127;
Integer integer128 = 128;

其均是调用java/lang/Integer.valueOf方法构建,valueOf代码如下:

1
2
3
4
5
public static Integer valueOf(int i) {
if (i >= IntegerCache.low && i <= IntegerCache.high)
return IntegerCache.cache[i + (-IntegerCache.low)];
return new Integer(i);
}

IntegerCache是什么东东,直接贴代码:

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
private static class IntegerCache {
static final int low = -128;
static final int high;
static final Integer cache[];

static {
// high value may be configured by property
int h = 127;
String integerCacheHighPropValue =
sun.misc.VM.getSavedProperty("java.lang.Integer.IntegerCache.high");
if (integerCacheHighPropValue != null) {
try {
int i = parseInt(integerCacheHighPropValue);
i = Math.max(i, 127);
// Maximum array size is Integer.MAX_VALUE
h = Math.min(i, Integer.MAX_VALUE - (-low) -1);
} catch( NumberFormatException nfe) {
// If the property cannot be parsed into an int, ignore it.
}
}
high = h;

cache = new Integer[(high - low) + 1];
int j = low;
for(int k = 0; k < cache.length; k++)
cache[k] = new Integer(j++);

// range [-128, 127] must be interned (JLS7 5.1.7)
assert IntegerCache.high >= 127;
}

private IntegerCache() {}
}

概括来说就是在JVM初始化的时候会默认生成一个value值在[-128,127]之间的一个Integer对象Cache。
在调用valueOf的时候会先去Cache中查找,若存在,则直接返回对应对象,若不存在则重新new一个对象。
new Integer(127)则是调用了Integer的构造函数进行初始化。

分析Compare值

1、compareA查看比那一过程,其主要是通过使用if_acmpne进行比较,该指令主要是比较地址块是否相同。由于两个是不同的地址快,所以返回为false
2、cmopareB,在比较之前integer127首先调用了java/lang/Integer.valueOf来获取对应int值,因为int是基本数据类型,所以在地址块比较中,两者是相同的。
3、compareC与 序号2相同
4、compareD由于初始化时,128超过了cache范围,所以使用Integer xx = 128均会生成一个新的Integer对象。比较他们的地址块,不相同,所以范围为false
5、compareE初始化值为127时,每次都是会返回cache中的同一个对象,所以在比较地址时会返回true.

总结

  1. 在对象比较中还是最好使用equal。
  2. Integer会初始化[-128,127]中的所有Integer对象,用于今后调用。

Jetty Jersey Guice with cross origin

Jetty Request URL 跨域问题

跨域问题的原因

跨域是指a页面想获取b页面资源,如果a、b页面的协议、域名、端口、子域名不同,或是a页面为ip地址,b页面为域名地址,所进行的访问行动都是跨域的,而浏览器为了安全问题一般都限制了跨域访问,也就是不允许跨域请求资源。
该部分内容可以参考 连接

跨域问题jetty静态资源解决办法

使用jetty静态资源跨域主要是解决加载js、css等资源的跨越问题。
解决类似问题可以为servlet增加一个Filter。简单的filter代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
public class CrossOriginFilter implements Filter {
public void init(FilterConfig filterConfig) throws ServletException {
}
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
// 必须的
HttpServletResponse response = (HttpServletResponse) servletResponse;
response.setHeader("Access-Control-Allow-Origin", "*");
filterChain.doFilter(servletRequest, response);
}
public void destroy() {
}
}

类似方法也可以使用jetty-servlets.jar中的CrossOriginFilter。web.xml配置文件如下:

1
2
3
4
5
6
7
8
9
10
11
12
<web-app ...>
...
<filter>
<filter-name>cross-origin</filter-name>
<filter-class>org.eclipse.jetty.servlets.CrossOriginFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>cross-origin</filter-name>
<url-pattern>/cometd/*</url-pattern>
</filter-mapping>
...
</web-app>

注意,maven使用的dependency为

1
2
3
4
5
<dependency>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-servlets</artifactId>
<version>${jetty.version}</version>
</dependency>

可以看到如图
response结果

跨域问题jersey请求解决办法

我使用的Guice与jersey相结合,要使通过jersey的请求能够带上对应的Response内容,需要在JerseyServletModule中增加对应的Filter。
代码如下:

1
2
bind(CrossOriginFilter.class).in(Singleton.class);
filter("/*").through(CrossOriginFilter.class);

访问后获取response即可获取。
亲测无问题。
附上github example地址jetty+jesery+guice example

Kafka topic leader = -1 or leader = none

Kafka topic 查询leader为-1

问题原因

该问题的主要原因是在创建topic时,该partition的所有Replica都宕机了,Leader会自动设置为-1.

解决方法

  1. 查看Kafka配置文件 /config/server.properties
    添加如下配置
1
auto.leader.rebalance.enable=true
  1. 重启kafka的broker:
    进入kafka的bin目录
> nohup ./kafka-server-start.sh ../config/server.properties &

JPA缓存类型

JPA缓存类型与原理

JPA缓存简介

缓存在一个对象的持久化过程中可以缓存对象或者它的数据。缓存同样影响着对象的一致性,如果你查找了一个对象,之后你查找了一个相同的对象,就会返回与刚才一致的对象(其指向的内存单元相同)
JPA 1.0 没有定义共享对象的缓存,其共享对象的缓存主要是有供应商自行提供。在JPA中,缓存主要存在于transaction或者扩展的持久化上下文来保护对象的一致性,但是JPA不必一定要在transaction或者持久化上下文中开启缓存。
JPA 2.0 定义了一个共享缓存。通过注解的形式 @Cacheable或者cacheable XML 属性的形式来开启或者停止缓存对象。
例如:

Example JAP 2.0 :

1
2
3
4
5
@Entity
@Cacheable
public class Employee{
...
}

同样的,你可以在jpa的persistence.xml文件中以添加属性的方式来配置全部持久化单元的缓存模式,
例如:

Example JAP 2.0 SharedCacheMode XML

1
2
3
<prsistence-unit name="GG">
<shared-cache-mode>NONE</shared-cache-mode>
</prsistence-unit>

对象一致性

对象一直性在java中的含义是:如果两个值x,y他们指向同一个逻辑单元,那么x和y相等。及指正指向相同的内存单元。
在JPA中,对象的一致维持在一个transaction,相同的EntityManager中。在JEE中,对象一致性只在同一个transaction中维持。

所以如下实例返回值为true

1
2
3
Employee employee1 = entityManager.find(Employee.class,123);
Employee employee2 = entityManager.find(Employee.class,123);
assert (employee1 == employee2 );

该值一直为true无论该值是如何获取的

1
2
3
Employee employee1 = entityManager.find(Employee.class, 123);
Employee employee2 = employee1.getManagedEmployees().get(0).getManager();
assert (employee1 == employee2)

在jpa中,对象一致性不会再不同的EntityManager中维持,每个EntityManager维持自己的持久化上下文和自己对象的transactional状态。
所以

如下的代码返回值为true

1
2
3
4
5
EntityManager entityManager1 = factory.createEntityManager();
EntityManager entityManager2 = factory.createEntityManager();
Employee employee1 = entityManager1.find(Employee.class, 123);
Employee employee2 = entityManager2.find(Enployee.class, 123);
assert(employee1 != employee2);

对象一致性是jpa的有点,他可以避免在应用程序中管理多个对象副本,并且可以避免当一个对象改变后其他副本不变。由于不同的EntityManager或者transactions(在JEE中)不能维持对象的唯一性,所以,每个transaction在其他用户的系统中必须相互隔离。无论如何,我们必须知道应用程序是拷贝、分离或者是合并对象。
一些JPA产品可能含有只读对象的概念,对象的一致性通过共享对象缓存中的EntityManager维系。

对象缓存

一个对象缓存就是缓存JAVA对象。对象缓存的优点是将对象按照其在java中的格式进行存储。所有属性都存放在对象层,当缓存命中后,无需做任何的转换。在EntityManager从cache拷贝或者塞入cache的时候,必须要保证他的tarnsaction的独立性。对象不需要被重新创建,他们之间的关联关系已经是有价值的了。
暂时数据可以通过对象缓存存储。他可以自动进行也可以指定。如果暂时数据是不需要的,你也必须在获取该对象的缓存时将其清除。
一些JPA提供商运行只读的查询来直接获取对象的缓存。一些提供商值运行对象缓存只读信息。如果只是查找,使用对象缓存可以大大的提高查找效率。
你也可以通过自制的缓存系统来缓存你的只读数据。
TopLink/EclipseLink:支持对象缓存。该对象缓存是默认的,但是可以设置全局或者有选择的开启或停止。在persisitenc unit属性中的eclipselink.cache.shared.default可以设置为false来停止缓存。只读查询语句可以通过在查询语句中使用提示eclipselink.read-only来标注。Entity中可以通过标注@ReadOnly来标注只读。

数据缓存

数据缓存缓存对象的数据而非对象。该数据为该对象在数据库中的一行数据。数据缓存操作简单,并且你无需担心对象关系,数据一致性或者缓存管理。 数据缓存的缺点是当应用程序使用该数据时,其并不存储,且并不存放关联。这意味着,在内存中命中了对象,也需要从数据库中查找数据和其关联。一些提供商提供了数据缓存、关系缓存和查询缓存来满足缓存数据间的关系。

缓存关联

一些提供商支持一些分离的缓存来存放关联关系,这些支持OneToManyManyToMany关联关系。由于OneToOneManyToOne关联关系会涉及到对象的Id,通常是不需要被缓存的,但是一个反向的OneToOne由于它涉及到的不是主键而是外键,所以需要将该关联关系缓存起来。
关联缓存的缓存结果通常只缓存关联对象的Id而非对象或者数据。关联缓存主要存放资源对象的Id和关系的名称。有时,如果数据存储存放的数据结果而非数据行时,关联关系被作为数据缓存的一部分。当关联缓存被命中了,相关的对象将会在数据缓存中一个个的被查找出来。但是,如果相关对象不在数据缓存中,那么它将必须在所选的数据库中查找,这将会严重影响数据库的效率,因为它需要一个一个的读取数据。

缓存类型

有很多不同的缓存类型。最常见的是LRU和MRU。
一些cache类型包括如下:

  • LRU :在内存中保留N个最近使用的对象
  • FULL:永远存放所有读到的内容。
  • Soft:当内存很低时,使用JAVA垃圾收集机制来提示缓存释放对象
  • Weak:与对象缓存相关,在缓存中存放所有当前使用的对象
  • L1 :涉及到事务缓存(事务缓存是EntityManager的一部分,并非共享缓存)
  • L2 : 为共享缓存,概念上的存放在EntityManagerFactory,它控制所有的EntityManager
  • 数据缓存 : 数据行存储
  • 对象缓存 : 直接缓存对象
  • 关联关系缓存:缓存对象的关联关系
  • 查询缓存: 缓存一个查询语句的结果集合
  • 只读 :缓存只存放只读对象
  • 读写 :缓存可以处理插入、更新和删除
  • 事务 :缓存可以处理插入、更新和删除,并且满足事务的ACID
  • 集群 :当一个对象更新或者删除后,使用JMS、JGroups或者其他的机制来向其他集群中的服务广播失效信息。
  • 副本 :当一个对象读入任何一个服务缓存中时,使用JMS、JGroups或者其他的机制来通知所有服务器
  • 分布式:通过集群,快速的传播缓存对象,并且可以在另外的服务器缓存中查到该对象。

TopLink/EclipseLink:支持L1、L2、LRU、Soft、Full、Weak、查询缓存。对象缓存支持读写的和事务。同时通过JMS和RMI支持集群缓存。

查询缓存

查询缓存缓存查询结果而非对象。对象缓存通过缓存对象的Id,所以对于查询不是用Id作为查询条件的查询操作并不有效。一些对象存储支持二级索引,但是即使如此对于一些返回大量对象的查询操作,这仍然不是很有效。你必须通过数据库来确保你获取了所有对象。这就是为什么查询缓存是非常有用的而不仅仅通过存放对象的Id,查询结果需要被缓存。缓存的key是基于查询名称和变量。所以如果你有NamedQuery一直执行,你可以缓存他的结果,而且你只要执行查询语句一次即可。
查询缓存的主要问题是,它会缓存旧数据。查询缓存通常会与对象缓存相互作用来确保对象在对象缓存中是否过时。查询缓存有一些无效选项,这个对象缓存很像。
TopLink / EclipseLink:通过在查询操作中暗示 eclipselink.query-results-cache来开启。

旧数据

!!缓存的主要问题是如何同步原始数据(The main issue with caching anything is the possibility of the cache version getting out of synch with the original. )!!。这涉及到作为旧数据或者不同步的数据。对于只读数据,这不存在问题,但是对于频繁进行数据改变的,这可能是一个主要问题。这里有很多方式来处理旧数据和未同步数据。

一级缓存

在事务过程中或者请求中缓存对象的状态并不是一个问题。这通常称为一级缓存,或者说是EntityManager缓存,并且通过正确的JPA事务语义完成。如果你读同一对象两次,你必须获取同一对象,和相同的内存改变。问题仅仅发生在查询和DML过程中。

通过数据库查询,查询不会引入未写入的对象的状态。例如,你要持久化一个新的对象,但是JP还没有将该对象存入数据库,因为它只会在事务提交的时候才会写入数据库。所以你的查询将无法返回一个新的对象,因为他查找的是数据库而非一级缓存中。对于这个问题,用户可以调用flush()操作或者flushMode自动触发刷新操作。在EntityManager或者QueryflushMode的默认情况是要触发一个刷新操作,但是当一个写操作在查询操作还没有释放时,该操作会无法触发刷新。一些JPA提供商支持当对象在内存中更改了后,JPA确认数据库查询结果。这可以直接获取数据而不是要触发刷新操作。这可以对简单的查询操作起作用,但是对于复杂的查询,这将会大大提高查询的复杂程度。由于应用程序查询数据都是在更改数据之前,或者不需要查询已有对象,所有这并不是一个问题。
如果你绕过JPA执行DML直接操作数据库,或者通过原生的SQL查询,JDBC或者JPQLUPDATE或者DELETE查询,那么数据库可能会与一级缓存不同步。如果你在执行DML之前已经获取了对象,他们会有一个旧的状态并且不会包括改变的信息。确保你做的操作是正确的,否则你可能必须重新刷新受影响的数据。

一级缓存,或者EntityManager缓存在JPA中可以跨越事务的界限。一个JTA管理EntityManager将只要存在于该JTA事务在JEE中的整个过程。JEE服务将会将应用的代理注入进一个EntityManager,当开启一个JTA事务时,一个新的EntityManager将会被创建,或者被清楚。一级缓存在一个EntityManager创建使用过程中一直存在,如果一直长时间的维持它,它可能会含有旧数据,甚至是内存漏洞和低效。在每个请求时创建一个新的EntityManager是一个很好的主意。一级缓存同样可以用EntityManager.clear()方法或者调用EntityManager.reflush()方法来实现清空的操作。

二级缓存

二级缓存包括了事务,EntityManager,他不是JPA必要的一部分。大多数JPA提供商支持二级缓存,但是实施和使用的语法上有一些不同。一些JPA提供商默认开启二级缓存。
如果系统只是一个提供访问数据库的应用程序或者服务,那么在使用二级缓存过程中问题不大。因为他应该经常会更新。但是主要的问题是在使用DML,如果应用直接通过原生SQL语句,JDBC,或者JPQL1的UPDATE或者DELETE语句。JPQL查询会自动使二级缓存数据无效,但是这可能需要基于JPA的提供商。如果你使用了原生的DML查询或者直接用JDBC,你需要通过刷新、清除或者其他方式使受影响的数据无效。
如果还有其他应用或者应用服务访问相同数据库,那么旧数据将会成为一个大问题。只读对象或者插入新对象不会发生问题。新对象一硬格从其他服务通过缓存数据来访问数据库而得到。这通常只使用find()操作和关联命中缓存。其他应用或者服务更新和删除对象会使数据成为旧数据。
由于更新对象,任何查询该对象的操作都可能返回旧的数据。在更新对象过程中或者由于一个用户在未使用锁的情况下,覆盖了另一个用户的更改信息这都会触发乐观锁。当然如果只有一个应用或者服务访问数据库,在不使用缓存的情况下也是可能发生的。这就是使用乐观锁的重要意义。当然,旧数据也肯能使用户获取到。

刷新

刷新是最常用的更新旧数据的方法。大多数应用软件用户很数据缓存的概念,并且知道何时他们需要刷新数据,并且乐于点击刷新按钮。这个在浏览器非常常见,大多数浏览器有其访问网页的缓存,从而避免加载同一界面两次,除非用户点击刷新按钮。相同的理论也可以在构建JPA应用时使用。JPA提供商有很多刷新操作。
一些JPA提供商在二级缓存上支持刷新选项。一个选项是在任何查询数据操作后都刷新。这意味着find()操作将仍然通过缓存。但是如果查询数据库或者获取数据库数据,二级缓存将会刷新数据。这避免了查询旧数据。但是这也使缓存失去了意义。刷新的代价不光光是刷新对象还要刷新他们的关联关系。一些JPA提供商提供了该属性整合乐观锁。如果该数据在数据库行中的值比在缓存中的版本值新,那么他会刷新旧数据,否则缓存值将会被返回。这个属性提供了优化的缓存,从而避免查询到旧数据。然而通过find()或者通过关联获取的数据任然是旧值。一些JPA提供商也允许find()操作可配置成首先核对数据库,但是这违背了cache的目的。

JPA 2.0 内存 API

JPA 2.0提供了一组标准的查询提示来刷新或者忽略cache。这些查询提示在CacheRetrieveMode和CacheStoreMode定义。
Query hints:

  • javax.persistence.cache.retrieveMode : CacheRetrieveMode
    • BYPASS : 忽略缓存,直接从数据库中获取内容
    • USE : 允许使用cache,如果对象/数据已经在cache中,则使用在cache中的数据
  • javax.persistence.cache.storeMode : CacheStoreMode
    • BYPASS : 不缓存数据库的查询结果
    • REFRESH : 如果对象/数据已经存在于cache中,用数据库中查询结果刷新/代替这些数据。
    • USE :缓存查询的结果
缓存设置实例
1
2
3

Query query = em.createQuery("Select e from Employee e");
query.setHint("javax.persistence.cache.storeMode",CacheStoreMode.REFRESH);

JPA2.0也提供了Cache接口,用户可以利用EntityManagerFactorygetCache()方法得到Cache接口。该Cache接口可以用来人工的删除在cache中的实例。
无论是一个特殊的实例、一个完整的类或者一个完整的缓存都可以被删除。该接口同时也可以判断该实例是否存在。
一些JPA生产厂商或扩展getCache()来提供额外的API。
TopLink / EclipseLink : 提供一个额外的缓存接口JpaCache。提供额外的API进行失效、查询缓存、删除缓存操作。

缓存清除实例
1
2
Cache cache = factory.getCache();
cache.evict(Employee.class, id);

缓存无效

常用的使缓存失效的方式是设置其缓存为无效状态。设置一定的时间,一天中特定的次数之后,删除或者是设置其无效。 超时失效保证了应用系统将不会再数据超时之后再从缓存中获取。这个时间可以在应用中设置。用户可以设置每天的失效时间,这可以确保一天的数据是新的。如果一个批处理在晚上更新,这时间可以设置为该作业之后。数据也可以人工的设置失效类似于使用JPA 2.0中的evict()API。
大多数内存的实现提供了一些失效的方式。JPA没有定义任何失效的配置参数,所有这些配置参数都是基于JPA和cache的提供商。
TopLink / EclipseLink :通过使用@Cache标注和orm.xml中的<cache>元素提供对失效时间的设置。失效时间也可以通过API来设置,也可用于集群中设置。

集群中的缓存

在集群,各个机器将会直接更新数据库而不是更新其他机器的缓存,每个机器的cache很容易过期,所以在集群中缓存是很困难的。但是这并不意味这在集群中无法使用缓存,你必须认真的配置它的参数。
对于只读对象的缓存可以一直使用。对于查看大多数对象可以使用缓存,但是一些场景需要避免使用旧数据。如果旧数据只是在写操作过程中存在问题,可以使用乐观锁来避免发生旧数据的写入。当乐观锁的异常发生,一些JPA提供商会自动更新或者使当前数据在cache中失效。所有如果用户或者应用再次执行该事务时,写操作的时候将会成功。你的应用也可以捕获锁异常并且刷新或者使该对象失效,或者潜在的重试该事务(如果用户不需要关注锁错误)。缓存失效可以通过设置数据在缓存中的存活时间来减少旧数据的可能性。缓存的大小也影响旧数据。
虽然对于用户,返回旧数据是一个问题,但是当用户刚刚更新后,返回值为旧数据是一个更大的问题。这个可以通过设置session affinity来避免。但是必须确保用户在他们session过程中是和集群中的同一台机器做操作的。通常的操作是在页面上增加一个刷新按钮,这可以允许用户手动刷新。这些应用可以选择在更新重要数据之后进行刷新对象操作,类似的如在执行更新操作时使用只读的查询。
对于以写操作为主的对象,禁止那些对象的缓存是最好的解决方法。缓存对插入和更新是没有益处的。缓存操作将会增加很多额外的写操作,因为你必须要更新缓存,还要处理大量的缓存垃圾。所以如果cache没有提供帮助,你应该关闭它。如果对象含有一系列复杂的关系,只有很少一部分需要更新,那么缓存还是很有意义的。

缓存一致性

解决季军缓存的一个解决方法是通过消息框架在集群中各个机器之间协作。JMS或者JGroups可以与JPA或者应用时间结合,当一个更新发生时,利用广播通知其他机器。一些JPA提供商支持集群环境缓存的协作。
TopLink / EclipseLink : 利用JMS或者RMI 支持集群协作。通过使用@Cache注释或者在orm.xml中的<cache>并且使用持久化单元的属性eclipselink.cache.coordination.protocol完成集群缓存设置。

分布式缓存

一个分布式缓存是跨机器的缓存。每个对象将会存放在一个或者一批机器上。这可以避免旧数据,因为该缓存是从同一个地方获取更新数据的,所以它的数据始终是最新的。这个解决方法的重点是缓存需要网络通信。该解决方案当所有机器在集群中都相连并且有相同高速的网速,并且数据库服务器也有很好的链接和加载的情况下,该方法是很好的。分布式缓存环节了数据库访问,它可以使应用不受数据库的瓶颈扩展到一个大集群。一些分布式缓存提供商提供了本地缓存,提供缓存间的协调。
TopLink : 支持与Oracle Coherence的分布式缓存集成。

缓存事务的独立性

当使用缓存后,缓存的一致性和独立性就与数据库的事务独立性一样重要。对于基本缓存独立性,缓存的必须在数据库事务已经提交后更新,否者未提交的数据不能被其他用户访问。
缓存也可以是透明或者非透明的。在一个透明缓存中,这些从一个事务提交到缓存的变化作为一个简单的原子单元。这就意味这对象/数据必须先在缓存中加锁。当更新缓存后解锁。理性情况下,在事务执行之前获取锁,来确保与数据库中的一致性。非透明缓存在非加锁情况下,一步一步更新对象/数据。这意味着会有短暂一段时间,数据库中的内容和缓存中是数据不一致。这些都需视情况而定。
乐观锁定在高速缓存中的隔离的另一个重要的考虑因素。如果使用乐观锁,该缓存应该避免用旧数据替换新数据。当在读取的时候,系统正在更新数据库这种情况下,乐观锁非常重要。
一些JPA提供少运行配置他们缓存隔离。或者不同的缓存制定不同的隔离级别。