一、特殊名词介绍
无
二、背景、场景介绍
帆软报表、决策平台的相关管理数据都是保存在finedb中的(可以外置)。开发者在开发插件时也可以通过DBAccessProvider接口对finedb的表进行扩展,避免出现需要使用者手动建表的情况。
三、接口介绍
package com.fr.db.fun;
import com.fr.plugin.db.accessor.DBAccessible;
import com.fr.stable.fun.mark.Mutable;
/**
* Created by loy on 2018/8/21.
*
* 用于插件注册自定义数据库表的接口
*/
public interface DBAccessProvider extends Mutable, DBAccessible {
String XML_TAG = "DBAccessProvider";
int CURRENT_LEVEL = 1;
}
package com.fr.plugin.db.accessor;
import com.fr.stable.db.dao.DAOProvider;
/**
* Created by loy on 2018/9/29.
*/
public interface DBAccessible extends DBInjectable {
DAOProvider[] registerDAO();
}
package com.fr.plugin.db.accessor;
import com.fr.stable.db.accessor.DBAccessor;
/**
* Created by loy on 2018/8/23.
*
* 数据库访问注入接口
*/
public interface DBInjectable {
/**
* 当可以访问数据库时的回调
* @param accessor 数据库访问接口
*/
void onDBAvailable(DBAccessor accessor);
}
关联接口
package com.fr.stable.db.dao;
import com.fr.stable.db.data.DataRecord;
import com.fr.stable.db.session.DAOSession;
import com.fr.stable.query.QueryFactory;
import com.fr.stable.query.condition.QueryCondition;
import com.fr.stable.query.data.DataColumn;
import com.fr.stable.query.data.DataList;
import com.fr.stable.query.restriction.RestrictionFactory;
import java.util.List;
import java.util.Map;
/**
* Created by loy on 2017/10/20.
* <p>
* 基础DAO
*/
public abstract class BaseDAO<T extends DataRecord> implements DAO<T> {
private DAOSession session;
public BaseDAO(DAOSession session) {
this.session = session;
}
@Override
public DAOSession getSession() {
return session;
}
/**
* 如果重写了getEntityClass方法,就可以不重写其他所有DAO<T>接口中的方法
* 推荐只重写getEntityClass方法,保留原写法作为兼容
*/
protected Class<T> getEntityClass() {
return null;
}
@Override
public void add(T record) throws Exception {
getSession().persist(record);
}
@Override
public T getById(String id) throws Exception {
checkEntityClass();
return getSession().getById(id, getEntityClass());
}
@Override
public void update(T record) throws Exception {
getSession().merge(record);
}
@Override
public void update(Map<String, Object> columnMap, QueryCondition queryCondition) throws Exception {
getSession().update(columnMap, queryCondition, getEntityClass());
}
@Override
public void remove(String id) throws Exception {
checkEntityClass();
getSession().remove(
QueryFactory.create().addRestriction(RestrictionFactory.eq(T.COLUMN_ID, id)),
getEntityClass()
);
}
@Override
public void remove(QueryCondition queryCondition) throws Exception {
checkEntityClass();
getSession().remove(queryCondition, getEntityClass());
}
@Override
public List<T> find(QueryCondition queryCondition) throws Exception {
checkEntityClass();
return getSession().find(queryCondition, getEntityClass());
}
@Override
public T findOne(QueryCondition queryCondition) throws Exception {
checkEntityClass();
return getSession().findOne(queryCondition, getEntityClass());
}
@Override
public DataList<T> findWithTotalCount(QueryCondition queryCondition) throws Exception {
checkEntityClass();
return getSession().findWithTotalCount(queryCondition, getEntityClass());
}
@Override
public long count(QueryCondition queryCondition) throws Exception {
checkEntityClass();
return getSession().count(queryCondition, getEntityClass());
}
@Override
public List<Object> findInProjection(QueryCondition queryCondition, String... columns) throws Exception {
checkEntityClass();
return getSession().findInProjection(queryCondition, getEntityClass(), columns);
}
@Override
public List<Object> findInProjection(QueryCondition queryCondition, DataColumn... columns) throws Exception {
checkEntityClass();
return getSession().findInProjection(queryCondition, getEntityClass(), columns);
}
@Override
public void addOrUpdate(T record) throws Exception {
if (record != null && getById(record.getId()) != null) {
update(record);
} else {
add(record);
}
}
private void checkEntityClass() {
if (getEntityClass() == null) {
throw new AssertionError("entity class is null, see introduction of BaseDAO#getEntityClass()");
}
}
}
package com.fr.decision.backup;
import com.fr.stable.db.data.DataRecord;
import com.fr.third.javax.persistence.Column;
import com.fr.third.javax.persistence.Id;
import com.fr.third.javax.persistence.MappedSuperclass;
/**
* Created by Zed on 2017/12/19.
* 基本实体对象
*/
@MappedSuperclass
public abstract class BaseEntity implements DataRecord {
public static final String COLUMN_ID = "id";
@Id
@Column(name = COLUMN_ID, nullable = false)
private String id = null;
@Override
public String getId() {
return id;
}
@Override
public void setId(String id) {
this.id = id;
}
}
package com.fr.stable.db.dao;
import com.fr.stable.db.data.DataRecord;
/**
* Created by loy on 2018/8/20.
*/
public interface DAOProvider<T extends DataRecord> {
Class<T> getEntityClass();
Class<? extends BaseDAO<T>> getDAOClass();
}
package com.fr.stable.db.accessor;
import com.fr.stable.db.action.DBAction;
/**
* Created by loy on 2018/8/21.
*
* 数据库访问器接口
*/
public interface DBAccessor {
/**
* 运行数据库查询操作(不开启事务)
*/
<T> T runQueryAction(DBAction<T> action) throws Exception;
/**
* 运行数据库增删改操作(开启事务)
*/
<T> T runDMLAction(DBAction<T> action) throws Exception;
}
package com.fr.stable.db.action;
import com.fr.stable.db.dao.DAOContext;
/**
* Created by loy on 2018/8/21.
*/
public interface DBAction<T> {
T run(DAOContext context) throws Exception;
}
四、支持版本
五、插件注册
<extra-core>
<DBAccessProvider class="your class name"/>
</extra-core>
六、原理说明
产品中会通过PluginDBManager向业务单元提供插件持久化的调用入口DBContext,在PluginDBManager初始化时,会加载插件中申明的所有PluginDBManager接口实例。
package com.fr.plugin.db;
import com.fr.properties.finedb.FineDBProperties;
import com.fr.log.FineLoggerFactory;
import com.fr.plugin.context.PluginContext;
import com.fr.plugin.db.base.BasePluginDBManager;
import com.fr.plugin.injectable.PluginModule;
import com.fr.stable.db.DBContext;
import com.fr.stable.db.session.DAOSession;
import com.fr.stable.db.session.DAOSessionStore;
import com.fr.stable.db.transaction.TransactionProvider;
import com.fr.stable.plugin.ExtraClassManagerProvider;
import com.fr.db.fun.DBAccessProvider;
import java.util.Set;
/**
* Created by loy on 2018/8/17.
*/
public class PluginDBManager extends BasePluginDBManager {
private static final PluginDBManager instance = new PluginDBManager();
public static PluginDBManager getInstance() {
return instance;
}
private PluginDBManager() {
}
private DBContext dbContext = DBContext.create();
private DAOSessionStore sessionStore = new DAOSessionStore(dbContext);
@Override
protected void onInit() {
ExtraClassManagerProvider provider = (ExtraClassManagerProvider) PluginModule.ExtraCore.getAgent();
Set<DBAccessProvider> dbAccessProviders = provider.getArray(DBAccessProvider.XML_TAG);
if (dbAccessProviders.size() > 0) {
for (DBAccessProvider dbp : dbAccessProviders) {
loadPluginEntities(dbp);
}
try {
dbContext.init(FineDBProperties.getInstance().get());
for (DBAccessProvider dbp : dbAccessProviders) {
prepareDB(dbp);
}
} catch (Exception e) {
FineLoggerFactory.getLogger().error(e.getMessage(), e);
}
}
}
@Override
protected void onDestroy() {
dbContext.destroy();
}
public boolean isDBActive() {
return dbContext.isRunning();
}
public DBContext getDbContext() {
return dbContext;
}
protected void dealDynamicPluginInstall(PluginContext pluginContext) {
Set<DBAccessProvider> dbAccessProviders = pluginContext.getRuntime().get(DBAccessProvider.XML_TAG);
if (dbAccessProviders == null || dbAccessProviders.isEmpty()) {
return;
}
for (DBAccessProvider dbAccessProvider : dbAccessProviders) {
loadPluginEntities(dbAccessProvider);
}
try {
if (dbContext.isRunning()) {
dbContext.reload();
} else {
dbContext.init(FineDBProperties.getInstance().get());
}
for (DBAccessProvider dbAccessProvider : dbAccessProviders) {
prepareDB(dbAccessProvider);
}
} catch (Exception e) {
FineLoggerFactory.getLogger().error(e.getMessage(), e);
}
}
protected void dealDynamicPluginUninstall(PluginContext pluginContext) {
Set<DBAccessProvider> dbAccessProviders = pluginContext.getRuntime().get(DBAccessProvider.XML_TAG);
if (dbAccessProviders == null || dbAccessProviders.isEmpty()) {
return;
}
for (DBAccessProvider dbAccessProvider : dbAccessProviders) {
unloadPluginEntities(dbAccessProvider);
}
try {
if (dbContext.getEntityClasses().isEmpty()) {
dbContext.destroy();
} else {
dbContext.reload();
}
} catch (Exception e) {
FineLoggerFactory.getLogger().error(e.getMessage(), e);
}
}
@Override
protected DBContext getDBContext() {
return dbContext;
}
@Override
protected TransactionProvider getTransactionProvider() {
return sessionStore;
}
@Override
protected DAOSession getDAOSession() {
return sessionStore.getDAOSession();
}
}
七、特殊限制说明
DBAccessProvider在实现时有一个void onDBAvailable(DBAccessor accessor)的接口方法,这个方法是一个注入方法,也就是通过这个入参DBAccessor让我们的插件获得实际调用数据库的能力。一般开发时会把这个对象保存到静态变量中缓存。【点击看示例】
在编写entity类时常用的几个注解
@Entity //该注解用于申明这是一个实体类
@Table(name = "plugin_demo") //申明数据库表对应的信息,一般只用指定表名即可
@TableAssociation(associated = true) //这是否是一张关联表,非关联表可以不加这个注解
public class DemoEntity extends BaseEntity {
public static final String COLUMN_KEY = "key";
public static final String COLUMN_BEAN = "bean";
@Column(name = COLUMN_KEY) //申明成员对应的字段信息,常用的属性有字段名、是否可以为空、是否是主键 等等的
private String key = null;
@Column(name = COLUMN_BEAN)
@Convert(converter = BeanConverter.class)//什么这是一个需要转换存储的字段。通过实现BaseConverter接口来进行数据库字段跟对象直接的转换。一般常见的就是枚举类型的转换了。
private DemoBean bean = null;
...
}
接下来是BaseDao的实现,实际上从纯技术实现上来说可以在BaseDao中实现的功能和方法几乎都可以在在DBAccessProvider中实现。不过一般我们希望开发者遵循以下的标准去进行开发:
所有对单个表的增删改查,以及对应的条件生成都放到BaseDao的派生类中去实现。Dao类向上层提供所有单一数据表的所有功能单元操作方法。
所有关联表的增删改查,全部在DBAccessProvider的实现类或者专门的DBController中去实现,尽可能不要在这一层中去写条件的拼接。该层向上层提供的就是单一业务单元的操作方法了。每一个方法至多有一个事务。【点击看示例】
与DBAccessProvider相似的还有一个EmbedDBAccessProvider接口,该接口设计之初是运行插件直接调用产品自身的DAO,操作产品本身的数据库表,实现与DBAccessProvider几乎一致。但是经过实践发现,该接口极易导致插件不稳定,因为用户直接去操作数据库表,非常容易引起相关的业务单元方法的监控和逻辑错误。所以目前在官方需求和商城插件中已经禁止使用这个接口了(当然如果只是使用这个接口,用途跟DBAccessProvider一致,没有对产品本身的表进行读写的话可以保留使用)。
八、常用链接
demo地址:demo-db-access-provider
九、开源案例
免责声明:所有文档中的开源示例,均为开发者自行开发并提供。仅用于参考和学习使用,开发者和官方均无义务对开源案例所涉及的所有成果进行教学和指导。禁止用于任何商业用途,若作为商用一切后果责任由使用者自行承担。
open-JSD-8016
open-JSD-7957
open-JSD-7858
open-JSD-7843
open-JSD-7868
open-JSD-7747
demo-db-access