您的位置:首页 > 数据库

GreenDao源码详解第一篇(Dao、Mater等类生成原理)

2017-09-29 11:35 435 查看
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);
}


先看看PropertyType.String是什么鬼

/**
* 枚举声明类型
* @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;
}

很明显吗,将属性名字添加到set集合中以确保属性名唯一性,然后创建Property的构造者PropertyBuilder ,最终返回构造者,有意思,这个框架对属性特性的添加使用了构造者模式,当参数过多了,为了让客户端用起来舒服所以采用此模式,接着将属性添加到属性集合中,索引为位置和属性名的索引位置一一对应。满足我们上面的猜想。那么接着看例子。
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;
}

完成所有的配置后,开始执行生成最终的java类,第二参数表示在当前路径下的产生java文件
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");
}


最重要的是注释的部分,根据ftl文件生成模板类,这的ftl是什么鬼呢?ftl是freemarker框架定义的一种语法规则,想生成什么东西(比如html,jsp,java文件等),只要写ftl文件满足它的要求那么再利用它提供的api,几句代码就可以实现动态的生成代码,ftl语法是什么用的呢?请移植上方看官方文档。
这里我们配置一下让freemarker找到我们的ftl文件
//声明配置的版本号
Configuration config = new Configuration(Configuration.VERSION_2_3_23);
//第二个参数设置ftl的保存路径
config.setClassForTemplateLoading(getClass(), "");


GreenDao的源码有给提供这些ftl文件,看图



那么咱们运行一下看看会产生哪些文件



由于例子中写了三张表,所以映射了三个表对象,那么不管有几张表,总会至少产生四个类,一个表映射对象类、一个表+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<Entity> 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<String, Object> 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<String, Object> additionalObjectsForTemplate)
throws Exception {
Map<String, Object> 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);

那么看一下ftl是怎么引用这两个对象的字段值的
<#-- @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}> {
用entity里面的属性替换掉当前,假如此时的entity是Customer,那么上面的经过转化就为
public class CustomerDao extends AbstractDao<Customer, Long> ,瞬间感觉FreeMarker很强大有没有!

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;
}


好了,下面解析的原理都是一样的,不再一 一阐述,看到这流程基本上就通了,想产生我们想要的数据库操作类的话,利用GreenDao的封装属性,把我们想建的表全部写进

Schema的Entry集合内,然后通过FreeMarker模板框架根据ftl文件解析生成我们需要的类,最终把我们需要的类拿过来直接操作数据库。生成流程就分析到这里,感兴趣的小伙伴,可以自己尝试解放双手,让模板为你自动写代码,毕竟程序员要学会高级的懒吗。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息