GreenDao官方为什么说自己的数据库框架运行快呢,首先,第一点这个框架不像其他框架通过运行期反射创建ORM映射,而是通过freemarker模板方式在编译期之前帮你生成可用的和数据库生成有关的表帮助对象,所以说这第一点就比其他数据库框架的速度快。不了解java模板freemarker用法的请去官方文档查看,freemarker官方。
首先看一下GreenDao的官方给我们提供的例子:
public static void main(String[] args) throws Exception {
//第一个参数数据库版本,第二个参数生成java的包名
Schema schema = new Schema(3, "de.greenrobot.daoexample");
addNote(schema);
addCustomerOrder(schema);
new DaoGenerator().generateAll(schema, "../GreenDao/src-gen");
}
private static void addNote(Schema schema) {
Entity note = schema.addEntity("Note");
note.addIdProperty();
note.addStringProperty("text").notNull();
note.addStringProperty("comment");
note.addDateProperty("date");
}
private static void addCustomerOrder(Schema schema) {
Entity customer = schema.addEntity("Customer");
customer.addIdProperty();
customer.addStringProperty("name").notNull();
Entity order = schema.addEntity("Order");
order.setTableName("ORDERS"); // "ORDER" is a reserved keyword
order.addIdProperty();
Property orderDate = order.addDateProperty("date").getProperty();
Property customerId = order.addLongProperty("customerId").notNull().getProperty();
order.addToOne(customer, customerId);
ToMany customerToOrders = customer.addToMany(order, customerId);
customerToOrders.setName("orders");
customerToOrders.orderAsc(orderDate);
}
可以看见我们如果想生成自己的数据库帮助类的话,那么首先得定义一个
Schema类,表明数据库的版本号,和要生成的java文件的包名,其次声明
Entity (这个类相当于表的映射类,即我如果要创建一张表的话,那么就创建一个Entity 对象),
一张表中肯定有很多列字段,那么每个字段都有其属性,那么字段属性就由
Property类承载,框架设计单一性原则。那么随便点一个添加属性进去看看咱们的推论是否准确,就以它
addStringProperty("name").notNull()
入手
public PropertyBuilder addStringProperty(String propertyName) {
return addProperty(PropertyType.String, propertyName);
}
/**
* 枚举声明类型
* @author Administrator
*
*/
public enum PropertyType {
Byte(true), Short(true), Int(true), Long(true), Boolean(true), Float(true), Double(true),
String(false), ByteArray(false), Date(false);
private final boolean scalar;
PropertyType(boolean scalar) {
this.scalar = scalar;
}
/** True if the type can be prepresented using a scalar (primitive type). */
public boolean isScalar() {
return scalar;
}
}
这是一个枚举类,可以看出它定义了很多类型,那么这些类型一看就是标记java字段类型到数据库表类型之间的映射,这也属于枚举的高级用法,那么接下来继续跟进代码
/**
* 将属性名字和属性特性加入
*
* @param propertyType
* @param propertyName
* @return
*/
public PropertyBuilder addProperty(PropertyType propertyType,
String propertyName) {
if (!propertyNames.add(propertyName)) {
throw new RuntimeException("Property already defined: "
+ propertyName);
}
PropertyBuilder builder = new PropertyBuilder(schema, this,
propertyType, propertyName);
properties.add(builder.getProperty());
return builder;
}
private static void addCustomerOrder(Schema schema) {
Entity customer = schema.addEntity("Customer");
customer.addIdProperty();
customer.addStringProperty("name").notNull();
Entity order = schema.addEntity("Order");
order.setTableName("ORDERS"); // "ORDER" is a reserved keyword
order.addIdProperty();
Property orderDate = order.addDateProperty("date").getProperty();
Property customerId = order.addLongProperty("customerId").notNull().getProperty();
order.addToOne(customer, customerId);
ToMany customerToOrders = customer.addToMany(order, customerId);
customerToOrders.setName("orders");
customerToOrders.orderAsc(orderDate);
}
这个方法较第一个方法的特别之处就是声明了客户表和订单表是一对多的关系,那么看一下,首先order表添加了一个customerid字段也就是外键(即
customer 的id
),表明
order
是多,
customer 是1
public ToOne addToOne(Entity target, Property fkProperty) {
if (protobuf) {
throw new IllegalStateException(
"Protobuf entities do not support realtions, currently");
}
//外键属性
Property[] fkProperties = { fkProperty };
ToOne toOne = new ToOne(schema, this, target, fkProperties, true);
toOneRelations.add(toOne);
return toOne;
}
将
schema,order对象,目标
target(即customer对象
)
对象
,
外键
fkProperties
属性特性等拿来创建
ToOne
对象,然后加入
toOneRelations集合,一个表可以和多个表产生关系了,再看一下
customer.addToMany
public ToMany addToMany(Entity target, Property targetProperty) {
Property[] targetProperties = { targetProperty };
return addToMany(null, target, targetProperties);
}
看注释最终创建
ToMany对象多端和1端都持有
ToMany
的引用
public ToMany addToMany(Property[] sourceProperties, Entity target,
Property[] targetProperties) {
if (protobuf) {
throw new IllegalStateException(
"Protobuf entities do not support relations, currently");
}
/**
* 创建ToMany对象
*/
ToMany toMany = new ToMany(schema, this, sourceProperties, target,
targetProperties);
//加入集合
toManyRelations.add(toMany);
//同时多的一方也将ToMany保存到集合中
target.incomingToManyRelations.add(toMany);
return toMany;
}
new DaoGenerator().generateAll(schema, "../GreenDao/src-gen");
看看这个类的构造方法
public DaoGenerator() throws IOException {
System.out.println("greenDAO Generator");
System.out.println("Copyright 2011-2016 Markus Junginger, greenrobot.de. Licensed under GPL V3.");
System.out.println("This program comes with ABSOLUTELY NO WARRANTY");
patternKeepIncludes = compilePattern("INCLUDES");
patternKeepFields = compilePattern("FIELDS");
patternKeepMethods = compilePattern("METHODS");
//通过freemarker的api获取Configuration配置
Configuration config = getConfiguration("dao.ftl");
//得到模板dao.ftl的模板
templateDao = config.getTemplate("dao.ftl");
//得到dao-master.ftl的模板
templateDaoMaster = config.getTemplate("dao-master.ftl");
//得到dao-session.ftl的模板
templateDaoSession = config.getTemplate("dao-session.ftl");
//得到entity.ftl的模板
templateEntity = config.getTemplate("entity.ftl");
//得到dao-unit-test.ftl的模板
templateDaoUnitTest = config.getTemplate("dao-unit-test.ftl");
//得到content-provider.ftl的模板
templateContentProvider = config.getTemplate("content-provider.ftl");
}
这里我们配置一下让freemarker找到我们的ftl文件
//声明配置的版本号
Configuration config = new Configuration(Configuration.VERSION_2_3_23);
//第二个参数设置ftl的保存路径
config.setClassForTemplateLoading(getClass(), "");
那么咱们运行一下看看会产生哪些文件
由于例子中写了三张表,所以映射了三个表对象,那么不管有几张表,总会至少产生四个类,一个表映射对象类、一个表+Dao类、一个DaoSession类、一个DaoMaster的类,正好对应了模板解析的ftl文件
//得到模板dao.ftl的模板
templateDao = config.getTemplate("dao.ftl");
//得到dao-master.ftl的模板
templateDaoMaster = config.getTemplate("dao-master.ftl");
//得到dao-session.ftl的模板
templateDaoSession = config.getTemplate("dao-session.ftl");
//得到entity.ftl的模板
templateEntity = config.getTemplate("entity.ftl");
每个文件生成对应格式的类。那么继续先看
templateDao 怎么生成的
public void generateAll(Schema schema, String outDir, String outDirEntity, String outDirTest) throws Exception {
long start = System.currentTimeMillis();
File outDirFile = toFileForceExists(outDir);
File outDirEntityFile = outDirEntity != null ? toFileForceExists(outDirEntity) : outDirFile;
File outDirTestFile = outDirTest != null ? toFileForceExists(outDirTest) : null;
//初始化并检查一些参数(例如表名没有设置,那么根据类名产生默认值,检查字段的类型是否是枚举声明的那些类型)
schema.init2ndPass();
//初始化并检查一些参数(例如表名没有设置,那么根据类名产生默认值,检查字段的类型是否是枚举声明的那些类型)
schema.init3rdPass();
System.out.println("Processing schema version " + schema.getVersion() + "...");
/**
* 开始遍历表对象
*/
List entities = schema.getEntities();
for (Entity entity : entities) {
//创建表操作类,表+Dao
generate(templateDao, outDirFile, entity.getJavaPackageDao(), entity.getClassNameDao(), schema, entity);
if (!entity.isProtobuf() && !entity.isSkipGeneration()) {
//创建表对象
generate(templateEntity, outDirEntityFile, entity.getJavaPackage(), entity.getClassName(), schema, entity);
}
if (outDirTestFile != null && !entity.isSkipGenerationTest()) {
String javaPackageTest = entity.getJavaPackageTest();
String classNameTest = entity.getClassNameTest();
File javaFilename = toJavaFilename(outDirTestFile, javaPackageTest, classNameTest);
if (!javaFilename.exists()) {
//创建测试类
generate(templateDaoUnitTest, outDirTestFile, javaPackageTest, classNameTest, schema, entity);
} else {
System.out.println("Skipped " + javaFilename.getCanonicalPath());
}
}
for (ContentProvider contentProvider : entity.getContentProviders()) {
Map additionalObjectsForTemplate = new HashMap<>();
additionalObjectsForTemplate.put("contentProvider", contentProvider);
generate(templateContentProvider, outDirFile, entity.getJavaPackage(), entity.getClassName()
+ "ContentProvider", schema, entity, additionalObjectsForTemplate);
}
}
//创建master类
generate(templateDaoMaster, outDirFile, schema.getDefaultJavaPackageDao(),
schema.getPrefix() + "DaoMaster", schema, null);
//创建session类
generate(templateDaoSession, outDirFile, schema.getDefaultJavaPackageDao(),
schema.getPrefix() + "DaoSession", schema, null);
long time = System.currentTimeMillis() - start;
System.out.println("Processed " + entities.size() + " entities in " + time + "ms");
}
generate(templateDao, outDirFile, entity.getJavaPackageDao(), entity.getClassNameDao(), schema, entity);
方法创建表操作的帮助类
private void generate(Template template, File outDirFile,
String javaPackage, String javaClassName, Schema schema,
Entity entity, Map additionalObjectsForTemplate)
throws Exception {
Map root = new HashMap<>();
// 声明两个对象填入,最终会在ftl中以$被引用
root.put("schema", schema);
root.put("entity", entity);
if (additionalObjectsForTemplate != null) {
root.putAll(additionalObjectsForTemplate);
}
try {
File file = toJavaFilename(outDirFile, javaPackage, javaClassName);
// noinspection ResultOfMethodCallIgnored
file.getParentFile().mkdirs();
if (entity != null && entity.getHasKeepSections()) {
checkKeepSections(file, root);
}
Writer writer = new FileWriter(file);
try {
//将参数传入并写入到文件字符流之中
template.process(root, writer);
writer.flush();
System.out.println("Written " + file.getCanonicalPath());
} finally {
writer.close();
}
} catch (Exception ex) {
System.err.println("Data map for template: " + root);
System.err.println("Error while generating " + javaPackage + "."
+ javaClassName + " (" + outDirFile.getCanonicalPath()
+ ")");
throw ex;
}
}
这里只传入了两个对象供ftl文件引用,最后通过template写入文件
root.put("schema", schema);
root.put("entity", entity);
<#-- @ftlvariable name="entity" type="org.greenrobot.greendao.generator.Entity" -->
<#-- @ftlvariable name="schema" type="org.greenrobot.greendao.generator.Schema" -->
这种语法是注释(<#-- -->)
<#assign toBindType = {"Boolean":"Long", "Byte":"Long", "Short":"Long", "Int":"Long", "Long":"Long", "Float":"Double", "Double":"Double", "String":"String", "ByteArray":"Blob", "Date": "Long" } />
这种语法声明一个
Map集合
<#if entity.toOneRelations?has_content || entity.incomingToManyRelations?has_content>
import java.util.List;
#if>
这种语法是if语句,是不是瞬间感觉FreeMarker语法规则很简单!看这句用到了我们传过来
entity,这句话的意思就是entity的一对一集合不为空,或者有1对多的集合不为空就导
入import java.util.List的包,
<#if entity.toOneRelations?has_content>
import java.util.ArrayList;
#if>
import android.database.Cursor;
import android.database.sqlite.SQLiteStatement;
import org.greenrobot.greendao.AbstractDao;
import org.greenrobot.greendao.Property;
<#if entity.toOneRelations?has_content>
import org.greenrobot.greendao.internal.SqlUtils;
#if>
import org.greenrobot.greendao.internal.DaoConfig;
import org.greenrobot.greendao.database.Database;
import org.greenrobot.greendao.database.DatabaseStatement;
<#if entity.incomingToManyRelations?has_content>
import org.greenrobot.greendao.query.Query;
import org.greenrobot.greendao.query.QueryBuilder;
#if>
<#if entity.javaPackageDao != schema.defaultJavaPackageDao>
import ${schema.defaultJavaPackageDao}.${schema.prefix}DaoSession;
#if>
<#if entity.additionalImportsDao?has_content>
<#list entity.additionalImportsDao as additionalImport>
import ${additionalImport};
#list>
#if>
<#if entity.javaPackageDao != entity.javaPackage>
import ${entity.javaPackage}.${entity.className};
#if>
<#if entity.protobuf>
import ${entity.javaPackage}.${entity.className}.Builder;
正好对应了OrderDao类的包
import java.util.List;
import java.util.ArrayList;
import android.database.Cursor;
import android.database.sqlite.SQLiteStatement;
import org.greenrobot.greendao.AbstractDao;
import org.greenrobot.greendao.Property;
import org.greenrobot.greendao.internal.SqlUtils;
import org.greenrobot.greendao.internal.DaoConfig;
import org.greenrobot.greendao.database.Database;
import org.greenrobot.greendao.database.DatabaseStatement;
import org.greenrobot.greendao.query.Query;
import org.greenrobot.greendao.query.QueryBuilder;
public class ${entity.classNameDao} extends AbstractDao<${entity.className}, ${entity.pkType}> {
public class CustomerDao extends AbstractDao
public static class Properties {
<#list entity.propertiesColumns as property>
public final static Property ${property.propertyName?cap_first} = new Property(${property_index}, ${property.javaType}.class, "${property.propertyName}", ${property.primaryKey?string}, "${property.dbName}");
#list>
${property_index}索引从0开始,每次加1
<#list>标签遍历list,还是以Customer是Entry时举例
public static class Properties {
public final static Property Id = new Property(0, Long.class, "id", true, "_id");
public final static Property Name = new Property(1, String.class, "name", false, "NAME");
}
接下来声明变量
<#if entity.active>
private ${schema.prefix}DaoSession daoSession;
#if>
对应,默认
schema.prefix没有值。
private DaoSession daoSession;
继续遍历表的属性得到类型转化器,如果定义类型转化器专门为不是基本类型的字段可以定义类型转化器,例子中没有定义类型转化器,所以if不会走
<#list entity.properties as property><#if property.customType?has_content><#--
--> private final ${property.converterClassName} ${property.propertyName}Converter = new ${property.converterClassName}();
#if>#list>
接下来定义构造函数
public ${entity.classNameDao}(DaoConfig config) {
super(config);
}
public ${entity.classNameDao}(DaoConfig config, ${schema.prefix}DaoSession daoSession) {
super(config, daoSession);
<#if entity.active>
this.daoSession = daoSession;
#if>
}
对应
public OrderDao(DaoConfig config) {
super(config);
}
public OrderDao(DaoConfig config, DaoSession daoSession) {
super(config, daoSession);
this.daoSession = daoSession;
}