1 缓存
1.1 缓存代码示例
1)主配置文件打开缓存开关
<settings cacheModelsEnabled="true"/>
2)sqlmap配置文件
<sqlMap namespace="person">
<typeAlias alias="person" type="org.frank1234.ibatis.helloworld.Person"/>
<cacheModel id="personCache" type="LRU">
<property name="size" value="100"/>
</cacheModel>
<select id="getPerson" resultClass="person" cacheModel="personCache">
select id,name,sex,age from person where id=#value#
</select>
</sqlMap>
3)执行2次查询则会发现输出日志有如下:
2015-01-02 15:32:21,979: Cache 'person.personCache': cache miss
2015-01-02 15:32:22,218: {conn-100000} Connection
2015-01-02 15:32:22,229: {conn-100000} Preparing Statement: select id,name,sex,age from person where id=?
2015-01-02 15:32:22,256: {pstm-100001} Executing Statement: select id,name,sex,age from person where id=?
2015-01-02 15:32:22,256: {pstm-100001} Parameters: [2]
2015-01-02 15:32:22,256: {pstm-100001} Types: [java.lang.Integer]
2015-01-02 15:32:22,260: {rset-100002} ResultSet
2015-01-02 15:32:22,276: {rset-100002} Header: [id, name, sex, age]
2015-01-02 15:32:22,276: {rset-100002} Result: [2, frank1234, 男, 80]
2015-01-02 15:32:22,276: Cache 'person.personCache': stored object 'id=2,name=frank1234,age=80,sex=男'
id=2,name=frank1234,age=80,sex=男
2015-01-02 15:32:22,277: Cache 'person.personCache': retrieved object 'id=2,name=frank1234,age=80,sex=男'
id=0,name=null,age=0,sex=null
由输出可见,查询时先检查缓存中是否存在,第一次查询缓存中不存在,输出:Cache 'person.personCache': cache miss
然后从数据库中获取,获取后将对象缓存起来。输出:Cache 'person.personCache': stored object 'id=2,name=frank1234,age=80,sex=男'。
第二次查询由于查询的是同一个对象,缓存直接命中,不再访问数据库。输出:Cache 'person.personCache': retrieved object 'id=2,name=frank1234,age=80,sex=男'
1.2 使用缓存注意事项
1)一定要配置好flushOnExecute,使缓存中的脏数据失效,否则性能提升了,但是获取的是错误的数据对象,还不如不通过缓存提升性能呢。
2)注意直接操作sql造成的缓存失效
3)注意第三方系统操作数据造成的缓存失效。
4)集群部署造成的缓存失效。比如第一次查询走集群的节点A,删除操作走的是集群的节点B,这样节点A的缓存就是脏数据了。
所以缓存的使用要小心为妙,小心使得万年船。另外注意缓存使用的普世原则,即缓存适合读多写少,以及存在热点数据的场景。
1.3 其他配置参数
<cacheModel id="personCache" type="LRU" serialize="true" readOnly="true">
<flushInterval hours="24"/>
<flushOnExecute statement="updatePerson"/>
<property name="size" value="100"/>
</cacheModel>
1)serialize="true":是否使用全局的数据缓存,如果设置为false的话,则只对当前session有效。
2)readOnly="true" : 如果设置为true,则只要缓存中的数据对象的属性发生了变化,则此数据对象就从缓存中移除。
public static void get() throws Exception{
SqlMapClient client = IBatisUtil.getSqlMap();
Person person = new Person();
client.queryForObject("getPerson",new Integer(2),person);
person.setName("haha");
// System.out.println(person);
}
设置成true和false两次执行get()方法的输出差别:
设置成true,输出:
2015-01-02 15:54:07,245: Cache 'person.personCache': stored object 'id=2,name=frank1234,age=80,sex=男'
2015-01-02 15:54:07,245: Cache 'person.personCache': retrieved object 'id=2,name=haha,age=80,sex=男'
可见第二次get()从缓存中获取的name值变为了haha.
设置成false,输出:
2015-01-02 15:55:04,803: Cache 'person.personCache': stored object '[B@2e1f1f'
2015-01-02 15:55:04,806: Cache 'person.personCache': retrieved object 'id=2,name=frank1234,age=80,sex=男'
可见第二次get()从缓存中获取的name值仍然是frank1234
3)<flushInterval hours="24"/> :多长时间清除缓存,示例代表24小时强行清除缓存,如果不设置则代表不强行清除。
4)<flushOnExecute statement="updatePerson"/>:执行指定的statement时,将此cacheModel的缓存清空,请注意是全部清空此cacheModel。
比如先query的时候query的是id=2的Person对象,updatePerson修改的是id=3的Person对象,然后query id=2的对象,依然会查不到,因为执行updatePerson的时候整个chacheModel被清空了。
5)type取值:type可以设置为MEMORY/LRU/FIFO/OSCACHE。
MEMORY:
<cacheModel id="personCache" type="MEMORY" serialize="true" readOnly="false">
<flushInterval hours="24"/>
<flushOnExecute statement="updatePerson"/>
<property name="reference-type" value="STRONG"/>
</cacheModel>
value可以是STRONG/SOFT/WEAK。
分别对应Java的强引用/软引用和弱引用。
强引用代表只要对象没死宁可撑爆内存也不回收。
软引用代表垃圾回收的时候如果JVM内存不够用了,则回收软引用对象,否则不回收。
弱引用代表垃圾回收的时候不管JVM内存够不够用都回收。
LRU对应的有size属性,是对象满的时候清谁出去的算法,LRU代表最近最少使用的对象被干掉。
FIFO代表先进先出。
OSCACHE是集成第三方缓存实现。
2 关联查询
2.1 1:1关联查询
比如丈夫和妻子是1:1关系,天朝历来实行一夫一妻制,古代皇帝也是一夫一妻制,因为皇后只有一个,多会玩概念啊。
数据库表创建并构造数据:
create table husband (id varchar(10) not null,name varchar(20) not null,primary key (id));
create table wife (id varchar(10) not null,name varchar(20) not null,husbandid varchar(10) not null,primary key (id));
insert into husband(id,name) values('1','frank1234');
insert into wife(id,name,husbandid) values('1','frank12345',1);
Husband对象:
public class Husband implements Serializable{
public Husband(){}
private String id;
private String name;
private String wifeId;
private String wifeName;
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getWifeId() {
return wifeId;
}
public void setWifeId(String wifeId) {
this.wifeId = wifeId;
}
public String getWifeName() {
return wifeName;
}
public void setWifeName(String wifeName) {
this.wifeName = wifeName;
}
public String toString(){
return "id="+id+",name="+name+",wifeid="+wifeId+",wifename="+wifeName;
}
}
SqlMap配置文件:
<select id="getHusband" parameterClass="java.lang.String" resultClass="org.frank1234.ibatis.helloworld.Husband">
select a.id ,a.name ,b.id as wifeId,b.name as wifeName from husband a join wife b on a.id=b.husbandid
where a.id=#value#
</select>
查询代码:
SqlMapClient client = IBatisUtil.getSqlMap();
Husband husband = (Husband)client.queryForObject("getHusband","1");
System.out.println(husband);
输出结果:
id=1,name=frank1234,wifeid=1,wifename=frank12345
当然如果查询出有多条记录,也就说1个husband对应n个wife的场景,一夫多妻制了哈哈,男人们的梦想啊。
可以通过:
SqlMapClient client = IBatisUtil.getSqlMap();
List list = client.queryForList("getHusband","1");
for(int i=0;i<list.size();i++){
System.out.println((Husband)list.get(i));
}
输出:
id=1,name=frank1234,wifeid=1,wifename=frank12345
id=1,name=frank1234,wifeid=2,wifename=wife2
但是请注意,Husband对象始终是1:1的,如果要在Husband中添加一个List<Wife>属性,则这种方式就办不到了,得往下看。
2.2 1:n 关联查询
比如这个男人有钱有权了,脱贫致富进入了包二奶模式,即1:n关系。各位看官,此比喻存属玩笑,如是这样渣男一个,鉴定完毕。
数据库表创建并构造数据:
create table man (id varchar(10) not null,name varchar(20) not null,primary key (id));
create table lover (id varchar(10) not null,name varchar(20) not null,manid varchar(10) not null,primary key (id));
insert into man(id,name) values('1','frank1234');
insert into lover(id,name,manid) values('1','beauty1',1);
insert into lover(id,name,manid) values('2','beauty2',1);
Man对象:
public class Man implements Serializable {
private String id;
private String name;
private List<Lover> lovers;
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public List<Lover> getLovers() {
return lovers;
}
public void setLovers(List<Lover> lovers) {
this.lovers = lovers;
}
public String toString(){
return "id="+id+",name="+name;
}
}
不同的是其中的lovers属性是一个List对象。
Lover对象:
public class Lover implements Serializable{
private String id;
private String name;
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String toString(){
return "id="+id+",name="+name;
}
}
sqlMap配置文件:
<resultMap id="get-man-result" class="org.frank1234.ibatis.helloworld.Man">
<result property="id" column="id"/>
<result property="name" column="name"/>
<result property="lovers" column="id" select="getLoversByManId"/>
</resultMap>
<select id="getLoversByManId" parameterClass="java.lang.String" resultClass="org.frank1234.ibatis.helloworld.Lover">
select * from lover where manid=#value#
</select>
<select id="getMan" parameterClass="java.lang.String" resultMap="get-man-result">
select * from man where id=#value#
</select>
通过resultMap来关联查询另外一个sql。
<result property="lovers" column="id" select="getLoversByManId"/>其中select属性关联另外一个statement。column属性值对应的是man表中的字段,即用哪个字段的值去lover表中查询,即用哪个值传递给getLoversByManId这个Statement。
测试代码及输出结果:
SqlMapClient client = IBatisUtil.getSqlMap();
Man man = (Man)client.queryForObject("getMan","1");
List list = man.getLovers();
for(int i=0;i<list.size();i++){
System.out.println(man+"->>>>"+(Lover)list.get(i));
}
输出结果:
id=1,name=frank1234->>>>id=1,name=beauty1
id=1,name=frank1234->>>>id=2,name=beauty2
对于n:1方,例如Lover中要添加个Man的属性,也可以解决,代码如下:
Lover:
public class Lover implements Serializable{
private String id;
private String name;
private Man man;
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Man getMan() {
return man;
}
public void setMan(Man man) {
this.man = man;
}
public String toString(){
return "id="+id+",name="+name;
}
}
sqlmap配置文件如下:
<select id="getMan" parameterClass="java.lang.String" resultMap="get-man-result">
select * from man where id=#value#
</select>
<resultMap id="get-lover-result" class="org.frank1234.ibatis.helloworld.Lover">
<result property="id" column="id"/>
<result property="name" column="name"/>
<result property="man" column="manid" select="getMan"/>
</resultMap>
<select id="getLover" parameterClass="java.lang.String" resultMap="get-lover-result">
select * from lover where id=#value#
</select>
测试代码及输出如下:
SqlMapClient client = IBatisUtil.getSqlMap();
Lover lover = (Lover)client.queryForObject("getLover","1");
System.out.println(lover.getMan());
输出:
id=1,name=frank1234
3 延迟加载
<settings lazyLoadingEnabled="true" />
设置为true代表打开延迟加载开关。
例如对于之前1:n的关联查询。
SqlMapClient client = IBatisUtil.getSqlMap();
Man man = (Man)client.queryForObject("getMan","1");
List list = man.getLovers();
for(int i=0;i<list.size();i++){
System.out.println(man+"-》》》》"+(Lover)list.get(i));
}
如果lazyLoadingEnabled=false,则
执行Man man = (Man)client.queryForObject("getMan","1");的同时也执行select * from lover where manid=? 将lovers属性构造出来。如果用不着的话,就白白消耗了,所以最好别这样。
将lazyLoadingEnabled=true,执行List list = man.getLovers();的时候都不执行sql select * from lover where manid=?,只有在执行他的时候System.out.println(man+"-》》》》"+(Lover)list.get(i));,才执行上面的sql,也就是只有真正用到的时候才加载。
所以建议这个属性始终设置为true。