Java:基于ORM技术的配置文件持久化实现

概述:本文介绍了利用ORM(Object/Relational Mapper,对象关系映射)技术和Java中的注解、反射等特性实现配置文件的持久化,实现了实体类和配置文件的映射、配置文件的自动加载和自动保存。


项目地址:http://github.com/mayuanxiaonong/jtkPersistence


这个想法来源于之前项目的一个功能实现,当时要用swing做一个批量生成打印二维码的工具,需要对页边距、二维码数量、宽度、高度、间距等参数进行设置,以适应不同的打印纸,毫无疑问这些配置需要保存在配置文件中,利用Java的Properties类进行读取、保存。当时对这些参数进行了简单的封装:
/**
 * 二维码打印纸页面参数
 * @author Jason
 */
public class PrintParam {

    // 页面参数:左页边距、上页边距
    private double leftMargin;
    private double topMargin;
    // 二维码设置:行数、列数、宽、高、间距
    private int qrRows;
    private int qrCols;
    private double qrWidth;
    private double qrHeight;
    private double qrPadding;
    
    private PropertiesUtil p = new PropertiesUtil();
    
    /**
     * 加载配置
     */

    public void load() {
        leftMargin = p.getDoubleValue("Left_Margin");
        topMargin = p.getDoubleValue("Top_Margin");
        qrRows = p.getIntegerValue("QR_Rows");
        qrCols = p.getIntegerValue("QR_Cols");
        qrWidth = p.getDoubleValue("QR_Width");
        qrHeight = p.getDoubleValue("QR_Height");
        qrPadding = p.getDoubleValue("QR_Padding");
        System.out.println("PrintParam loaded ...");
        System.out.println(toString());
    }
    
    /**
     * 保存配置
     */

    public void save() {
        System.out.println("PrintParam saving ...");
        System.out.println(toString());
        p.setValue("Left_Margin", leftMargin);
        p.setValue("Top_Margin", topMargin);
        p.setValue("QR_Rows", qrRows);
        p.setValue("QR_Cols", qrCols);
        p.setValue("QR_Width", qrWidth);
        p.setValue("QR_Height", qrHeight);
        p.setValue("QR_Padding", qrPadding);
        p.store();
    }
    
    // getters & setters
    ...
}

虽然将参数封装到一个类中了,但是在读取、保存这些参数的时候还是需要将参数名字的字符串写在这里,而且读取和保存需要写到两个地方,封装的并不太好,而且如果在应用中使用了大量配置文件的话,读取和保存将是个很大的工作量。

所以当时就想到了开发这样一个工具类,可以将配置文件自动加载到实体类中,并能将实体对象自动保存到配置文件中,就像JPA持久化一样。

JPA的持久化实现了实体类和关系型数据库的映射,将实体类中的属性映射到数据表的字段。而配置文件是用哈希表存放的一堆键值对(Keys & Values),我们可以把配置文件看作是只有一条数据的数据表,其中的每一个键值对(K-V)的K就是数据表的字段名,而V就是数据表的字段值。

1、有了JPA做参照,实现起来就比较简单了,首先要设计一些自定义注解,用来标识实体类和字段:
1. @JtkEntity  标识某个实体类映射到一个配置文件,需要指定配置文件的名字。
2. @JtkKey  标识某个成员变量映射到配置文件的Key,需要指定Key的名字;或不使用JtkKey标注,默认使用变量名做Key的名字。另外,还可以指定字段的默认值。
3. @JtkNone  标识某个成员变量不映射到配置文件,效果通JPA的@Transient。

2、接下来就是要解析实体类,并自动加载配置文件了,这里使用Java的反射机制读取实体类的注解信息:
1. 读取JtkEntity注解
/**
 * Get the JtkEntity annotation of the entity class
 * 
 * @param cls
 *            Class of the entity class
 * @return
 * @throws JtkPersistenceException
 */

private static JtkEntity getJtkEntity(Class<?> cls) throws JtkPersistenceException {
    JtkEntity entity = cls.getAnnotation(JtkEntity.class);
    if (entity == null) {
        throw new JtkPersistenceException("Class " + cls.getName() + " is not a legal properties entity class!");
    }
    return entity;
}

2. 遍历实体类中的成员变量,通过@JtkKey注解或变量名得到配置文件中的字段名,使用配置工具类得到值:
Field[] fields = cls.getDeclaredFields();
for (Field f : fields) {
    // Jump static fields
    if ((f.getModifiers() & Modifier.STATIC) != 0) {
        continue;
    }

    // Jump @JtkNone fields
    if (f.getAnnotation(JtkNone.class!= null) {
        continue;
    }

    if (!isSupported(f.getType())) {
        throw new JtkPersistenceException("Unsupported type " + f.getType() + " of field " + f.getName());
    }

    // Get @JtkKey of field
    JtkKey key = f.getAnnotation(JtkKey.class);
    String keyName;
    String defaultValue;
    if (key != null) {
        keyName = key.name();
        defaultValue = key.defaultValue();
    } else {
        keyName = f.getName();
        defaultValue = "";
    }
    String keyValue = propertiesUtil.getValue(keyName, defaultValue);

根据变量的类型转换变量值:
// Cast value to type of field
Object value = null;
try {
    value = cast(f.getType(), keyValue);
catch (NumberFormatException e) {
    throw new JtkPersistenceException("Cast " + keyValue + " to " + f.getType() + " of field " + f.getName() + " failed!", e);
}

3. 利用反射设置变量的值:
try {
    f.setAccessible(true);
    f.set(object, value);
catch (IllegalArgumentException | IllegalAccessException e) {
    throw new JtkPersistenceException("Set value " + value + " to field " + f.getName() + " failed!", e);
}

3、同样的,使用反射机制可以获取一个对象中成员变量表示的字段名和字段值,并保存到配置文件中。


测试:
1. 配置文件 test.properties:
a=123
b=abc

2. 实体类 TestBean.java:
import  com.jtk.persistence.JtkEntity;
import com.jtk.persistence.JtkKey;
import com.jtk.persistence.JtkNone;

@JtkEntity(name = "bin/test.properties")
public class TestBean {

    @JtkKey(name = "a")
    private int aaa;
    private String b;
    @JtkNone
    private String c;

    public int getAaa() {
        return aaa;
    }
    public void setAaa(int aaa) {
        this.aaa = aaa;
    }
    public String getB() {
        return b;
    }
    public void setB(String b) {
        this.b = b;
    }

    @Override
    public String toString() {
        return "TestBean [aaa=" + aaa + ", b=" + b + ", c=" + c + "]";
    }
}

3. 测试类 Test.java:
import  com.jtk.persistence.JtkPersistenceException;
import com.jtk.persistence.JtkPersistence;

public class Test {

    public static void main(String[] args) throws JtkPersistenceException {
        TestBean bean = (TestBean) JtkPersistence.getInstance(TestBean.class);
        System.out.println(bean.toString());
        bean.setAaa(321);
        bean.setB(null);
        JtkPersistence.save(bean);
    }
}

运行结果:
field : aaa
keyName = a
keyValue = 321
defaultValue = 
field : b
keyName = b
keyValue = 
defaultValue = 
TestBean [aaa=321, b=null, c=null]
field : aaa
keyName = a
keyValue = 321
field : b
keyName = b
keyValue = 


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值