最近测试发现个IBatis 有个比较严重的性能问题, 描述如下:
1. define a bean class
public class Bean {
private int id;
private String desc;
private long price;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getDesc() {
return desc;
}
public void setDesc(String desc) {
this.desc = desc;
}
public long getPrice() {
return price;
}
public void setPrice(long price) {
this.price = price;
}
}
2. 如果在这个Bean定义中,存在一个属性没有 Getter方法 在运行过程中, IBatis 会表现为把 class Bean 确定成一个ComplexAccessPlan的对象。那么, IBatis对Bean对象填充SQL执行后的返回结果会造成比较严重的性能问题。 复杂对象填充Bean的结果在性能上表现比较差一点。 这个性能差异随着需要设置属性数量的增加, 性能成正比的下降。 目前我测试的结果是10个属性情况下影响大约 5-6%的执行时间, 如果属性增加到 84个(中文站的offer对象),他的性能会导致超过30%的下降。 具体的原因, 我会如下解释:
com.ibatis.sqlmap.engine.accessplan.AccessPlanFactory Line 60
if (bytecodeEnhancementEnabled) {
try {
plan = new EnhancedPropertyAccessPlan(clazz, propertyNames);
} catch (Throwable t) {
try {
plan = new PropertyAccessPlan(clazz, propertyNames);
} catch (Throwable t2) {
plan = new ComplexAccessPlan(clazz, propertyNames);
}
}
}
com.ibatis.common.beans.ClassInfo Line256
public Method getGetter(String propertyName) {
Method method = (Method) getMethods.get(propertyName);
if (method == null) {
throw new ProbeException("There is no READABLE property named '" + propertyName + "' in class '" + className + "'");
}
return method;
}
class EnhancedPropertyAccessPlan/PropertyAccessPlan call com.ibatis.common.beans.ClassInfo.getGetter(String) that cause an exception when a bean have no Getter method, AccessPlan object choose ComplexAccessPlan.
3. 根据以上的代码, 我们还可以得出如下结论(这是我给IBATIS开发团队的邮件部分, 不翻译:)):
IBtatis automatic decide a simple bean that property have no Getter method to be Complex type. IBatis does not prompt any warning enhancementEnable option will be skipped. I think these ibatis exception handling is not smooth. and If user's bean loose some Getter method, a common user does not know why ibatis performance become bad.
就是一个对象由于Getter方法的缺失, IBatis把这个对象的当做复杂对象, 从而, 导致enhancementEnable=true(bean对象字节增加功能, 有兴趣的同学可以看看CGLIB中BulkBean的使用)的定义失去了任何作用, 进一步导致IBATIS的性能下降。
针对我们发现的问题, 我们建议如下解决问题:
1. 任何被IBatis 使用的对象属性必须定义完整的Setter/Getter方法
2. 避免使用自定义类型的对象属性
3. 如果部分属性需要被适当处理后才能使用的, 比如表中有一个字段price, 但是我们需要使用的是Money对象, 请按如下方式使用。primitivePrice作为数据库使用的属性, price作为应用程序使用的属性。
public class Bean {
private Money price = null;
private long primitivePrice;
public Money getPrice() {
if (price == null) {
this.price = new Money(0, 0);
this.price.setCent(primitivePrice);
}
return price;
}
public void setPrice(Money price) {
if (price == null) {
this.price = new Money(0, 0);
} else {
this.price = price;
}
this.primitivePrice = price.getCent();
}
public void setPrimitivePrice(long price) {
this.primitivePrice = price;
}
public long getPrimitivePrice() {
return this.primitivePrice ;
}
}
1. define a bean class
public class Bean {
private int id;
private String desc;
private long price;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getDesc() {
return desc;
}
public void setDesc(String desc) {
this.desc = desc;
}
public long getPrice() {
return price;
}
public void setPrice(long price) {
this.price = price;
}
}
2. 如果在这个Bean定义中,存在一个属性没有 Getter方法 在运行过程中, IBatis 会表现为把 class Bean 确定成一个ComplexAccessPlan的对象。那么, IBatis对Bean对象填充SQL执行后的返回结果会造成比较严重的性能问题。 复杂对象填充Bean的结果在性能上表现比较差一点。 这个性能差异随着需要设置属性数量的增加, 性能成正比的下降。 目前我测试的结果是10个属性情况下影响大约 5-6%的执行时间, 如果属性增加到 84个(中文站的offer对象),他的性能会导致超过30%的下降。 具体的原因, 我会如下解释:
com.ibatis.sqlmap.engine.accessplan.AccessPlanFactory Line 60
if (bytecodeEnhancementEnabled) {
try {
plan = new EnhancedPropertyAccessPlan(clazz, propertyNames);
} catch (Throwable t) {
try {
plan = new PropertyAccessPlan(clazz, propertyNames);
} catch (Throwable t2) {
plan = new ComplexAccessPlan(clazz, propertyNames);
}
}
}
com.ibatis.common.beans.ClassInfo Line256
public Method getGetter(String propertyName) {
Method method = (Method) getMethods.get(propertyName);
if (method == null) {
throw new ProbeException("There is no READABLE property named '" + propertyName + "' in class '" + className + "'");
}
return method;
}
class EnhancedPropertyAccessPlan/PropertyAccessPlan call com.ibatis.common.beans.ClassInfo.getGetter(String) that cause an exception when a bean have no Getter method, AccessPlan object choose ComplexAccessPlan.
3. 根据以上的代码, 我们还可以得出如下结论(这是我给IBATIS开发团队的邮件部分, 不翻译:)):
IBtatis automatic decide a simple bean that property have no Getter method to be Complex type. IBatis does not prompt any warning enhancementEnable option will be skipped. I think these ibatis exception handling is not smooth. and If user's bean loose some Getter method, a common user does not know why ibatis performance become bad.
就是一个对象由于Getter方法的缺失, IBatis把这个对象的当做复杂对象, 从而, 导致enhancementEnable=true(bean对象字节增加功能, 有兴趣的同学可以看看CGLIB中BulkBean的使用)的定义失去了任何作用, 进一步导致IBATIS的性能下降。
针对我们发现的问题, 我们建议如下解决问题:
1. 任何被IBatis 使用的对象属性必须定义完整的Setter/Getter方法
2. 避免使用自定义类型的对象属性
3. 如果部分属性需要被适当处理后才能使用的, 比如表中有一个字段price, 但是我们需要使用的是Money对象, 请按如下方式使用。primitivePrice作为数据库使用的属性, price作为应用程序使用的属性。
public class Bean {
private Money price = null;
private long primitivePrice;
public Money getPrice() {
if (price == null) {
this.price = new Money(0, 0);
this.price.setCent(primitivePrice);
}
return price;
}
public void setPrice(Money price) {
if (price == null) {
this.price = new Money(0, 0);
} else {
this.price = price;
}
this.primitivePrice = price.getCent();
}
public void setPrimitivePrice(long price) {
this.primitivePrice = price;
}
public long getPrimitivePrice() {
return this.primitivePrice ;
}
}