Skip to content
ww20081120 edited this page May 10, 2024 · 8 revisions

特性

  1. O/R mapping不用设置xml,零配置便于维护
  2. 不需要了解JDBC的知识
  3. SQL语句和java代码的分离
  4. 可以自动生成SQL语句
  5. 接口和实现分离,不用写持久层代码,用户只需写接口,以及某些接口方法对应的sql 它会通过动态代理自动生成实现类
  6. 支持自动事务处理和手动事务处理
  7. Dao整合了Hibernate+mybatis的两大优势,支持实体维护和SQL分离
  8. SQL支持脚本语言
  9. 可以无缝集成Hibernate、Spring等第三方框架,也可以单独部署运行,适应性强。
  10. 支持JPA的CriteriaQuery API,并且提供了类似于Mybatis plus中 QueryWapper和LambdaQueryWapper的这种易用的写法。

快速上手

1、 项目的pom.xml中引入maven配置

<dependency>
	<groupId>com.hbasesoft.framework</groupId>
	<artifactId>framework-db-sh</artifactId>
	<version>${framework.version}</version>
</dependency>

2、在application.yml中配置数据源

master: #主数据库配置
 db:
  type: mysql # 数据库类型,支持oracle、h2、mysql 等等, 底层用Hibernate,其支持的改框架都支持
  url: jdbc:mysql://192.168.1.1:3306/member?useUnicode=true&characterEncoding=UTF-8&generateSimpleParameterMetadata=true&serverTimezone=Asia/Shanghai
  username: test # 用户名
  password: sgp # 密码,支持加密模式,密文需要用ENC(密文)包裹

3、具体使用参考下面的章节

PO (persistent object)的生成

db框架底层使用的Hibernate实现的,所以PO的规则和Hibernate保持一致, 可以使用第三方工具生成,我们也提供一个工具直接可以根据数据库中的表生成PO和DAO(Data Access Object)。

UI界面

  1. 直接运行com.hbasesoft.framework.db.cg.DBTable2JavaBean类中的main方法,会打开一个UI界面。
  2. 填写需要生成转化的表名称(不填会查询数据库中所有的表)
  3. 配置数据库的连接地址和用户名密码
  4. 点击生成
  5. 该工具最后还提供了密码加密工具,可以用于application.yml中数据源的密码加密

单表的增删改查

生成好的DAO会继承 com.hbasesoft.framework.db.core.BaseDao ,里面提供了大量的单表操作方法,当然如果不想使用这些方法,也可以不继承这个接口。

BaseDao.java

public interface BaseDao<T extends BaseEntity> {
    CriteriaBuilder criteriaBuilder(); // JPA工厂方法,做一些JPA支持的高级用法

    T get(Serializable id); // 根据id来获取数据
    <M> M getByCriteria(CriteriaQuery<M> criteria); // 根据条件查询,其中 criteria需要通过CriteriaBuilder来生成,具体参考JPA CriteriaQuery的用法
    T getBySpecification(CriterialQuerySpecification<T> specification); // 根据条件查询, PA CriteriaQuery简化写法
    T get(QuerySpecification<T> specification); // 根据条件查询, 参考Mybatis plus中 QueryWapper的用法
    T getByLambda(LambdaQuerySpecification<T> specification); // 根据条件查询,参考Mybatis plus中 LambdaQueryWapper的用法
    
    List<T> queryAll(); // 查询所有数据
    <M> List<M> queryByCriteria(CriteriaQuery<M> criteria); //根据条件查询
    List<T> queryBySpecification(CriterialQuerySpecification<T> specification); // 根据条件查询
    List<T> query(QuerySpecification<T> specification); // 根据条件查询
    List<T> queryByLambda(LambdaQuerySpecification<T> specification); // 根据条件查询
    <M> PagerList<M> queryPagerByCriteria(CriteriaQuery<M> criteria, int pageIndex, int pageSize); // 根据条件分页查询
    PagerList<T> queryPagerBySpecification(CriterialQuerySpecification<T> specification, int pageIndex, int pageSize); // 根据条件分页查询
    PagerList<T> queryPager(QuerySpecification<T> specification, int pageIndex, int pageSize); // 根据条件分页查询
    PagerList<T> queryPagerByLambda(LambdaQuerySpecification<T> specification, int pageIndex, int pageSize); // 根据条件分页查询
    
    void save(T entity); // 保存数据
    void saveBatch(List<T> entitys); // 批量保存
    
    void update(T pojo); // 更新数据
    void updateBatch(List<T> entitys); // 批量更新
    void updateByCriteria(CriteriaUpdate<T> criteria); // 根据条件来做更新
    void updateBySpecification(CriterialUpdateSpecification<T> specification); // 根据条件来做更新
    void update(UpdateSpecification<T> specification); // 根据条件来做更新
    void updateByLambda(LambdaUpdateSpecification<T> specification); // 根据条件来做更新
    
    void delete(T entity); // 删除数据
    void deleteById(Serializable id); // 根据id来删除
    void deleteBatch(Collection<T> entities);  //批量删除
    void deleteByIds(Collection<? extends Serializable> ids); // 根据id批量删除
    void deleteByCriteria(CriteriaDelete<T> criteria); // 根据条件删除
    void deleteBySpecification(CriterialDeleteSpecification<T> specification); // 根据条件删除
    void delete(DeleteSpecification<T> specification); // 根据条件删除
    void deleteByLambda(LambdaDeleteSpecification<T> specification); // 根据条件删除
} 

ICourseDao.java

@Dao
public interface ICourseDao extends BaseDao<CourseEntity> {
}

BaseDaoTester.java

/** 依赖spring 注入DAO */
@Resource
private ICourseDao iCourseDao;

/** 根据某个属性查询 */
@Test
public void getByProperty() {
      CourseEntity entity = iCourseDao.get(q -> q.eq(CourseEntity.COURSE_NAME, "语文").build());
      Assert.equals(entity.getId(), "1", ErrorCodeDef.SYSTEM_ERROR);
}

 /** 统计*/
@Test
public void countCourse() {
    CriteriaBuilder cb = iCourseDao.criteriaBuilder();
    CriteriaQuery<Long> query = cb.createQuery(Long.class);
    Root<CourseEntity> root = query.from(CourseEntity.class);
    query.select(cb.count(root));
    Long count = iCourseDao.getByCriteria(query);
    Assert.isTrue(count.intValue() == NUM_3, ErrorCodeDef.SYSTEM_ERROR);
}

复杂SQL查询及分页

多表之间的关联查询建议直接写SQL,SQL语句我们支持直接写在@Sql方法注解上, 也支持写成单独文件,文件名称为类名称_方法名称.sql、类名称_方法名称.数据库类型.sql 、数据库类型/类名称_方法名称.sql这三种格式。

IStudentDao.java

@Dao
public interface IStudentDao extends IBaseDao<StudentEntity> {

    void createTable();

    @Sql("select count(1) from t_student_course sc, t_course c "
        + "where sc.course_id = c.id and sc.score >= 60 and c.course_name = :courseName")
    int countCoursePass(@Param("courseName") String courseName);

    @Sql("select count(1) from t_student")
    int countStudentSize();

    /** 
     * 分页只要在方法上加上@Param(Param.PAGE_INDEX) int pageIndex, @Param(Param.PAGE_SIZE) int pageSize 
     * 两个参数就会自动分页,SQL中不用写任何关于分页的处理
     *  返回的List是com.hbasesoft.framework.db.core.utils.PagerList,这个list可以当成普通list使用,它的信息里面包含了分页所需要的参数<E>
     * /
    @Sql
    PagerList<StudentEntity> queryStudentCourse(@Param("entity") StudentEntity studentEntity,
        @Param(Param.PAGE_INDEX) int pageIndex, @Param(Param.PAGE_SIZE) int pageSize);
 }

多条SQL用;做分割, 注意结尾不能包含;

IStudentDao_createTable.sql

DROP TABLE IF EXISTS `t_student_course`;

DROP TABLE IF EXISTS `t_student`;

DROP TABLE IF EXISTS `t_course`;

CREATE TABLE `t_student` (
	id       varchar(32)       primary key,
	name     varchar(32)       not null,
	age      int(3)            not null
);

insert into t_student(id, name, age) values ('1', '张三', 18);
insert into t_student(id, name, age) values ('2', '李四', 19);
insert into t_student(id, name, age) values ('3', '王五', 18);
insert into t_student(id, name, age) values ('4', '赵六', 17)

sql内容使用了Velocity模版,支持使用#if #else等语法。注意:为了防止SQL注入,经过模版解析后的sql中的参数需要使用冒号来赋值。

IStudentDao_queryStudentCourse.h2.sql

select 
   s.id, s.name, s.age, sc.score, c.course_name
from 
   t_student s,
   t_course c,
   t_student_course sc
where
   s.id = sc.student_id
and
   c.id = sc.course_id
#if($entity.name)
  and s.name like :entity.name
#end

#if($entity.age)
  and s.age = :entity.age
#end

批量数据处理

超过一千条以上的数据需要使用 com.hbasesoft.framework.db.hibernate.IBaseDao.executeBatch(String sql, Collection<Object[]> objcts, int commitNumber) 方法来处理。

@Test
public void executeBatch() {
    int s1 = iStudentDao.countStudentSize();
    
    // 读取200万行的csv文件, 每次读取10000行
    IOUtil.batchProcessFile(new File("Student.csv"), line -> {
        if (StringUtils.isNotEmpty(line)) {
            String[] strs = StringUtils.split(line, GlobalConstants.SPLITOR);
            if (strs.length >= 2) {
                return new Object[] {
                    CommonUtil.getTransactionID(), strs[0], strs[1]
                };
            }
        }
        return null;
    }, (students, pageIndex, pageSize) -> {
         
         // 批量插入数据,每1000条提交一次
        iStudentDao.executeBatch("insert into t_student (id, name, age) values (?, ?, ?)", students,
            GlobalConstants.DEFAULT_LINES);
        return true;
    });
    int s2 = iStudentDao.countStudentSize();
    Assert.isTrue(s2 - s1 == NUM_200000, ErrorCodeDef.SYSTEM_ERROR_10001);
}

事务控制

框架支持手动和Spring注解两种方式来控制事务 1、 Spring 注解方式

/*
* 该注解可以打在方法上,也可以打在类上
*/
@Transactional(isolation=Isolation.REPEATABLE_READ,propagation=Propagation.REQUIRED,readOnly=false)
public void transfer(final Integer from, final Integer to, final Float money) {
	accountDao.subMoney(from,money);
	int i = 1/0;
	accountDao.addMoney(to,money);
}

2、 手动控制

// 初始化事务定义
DefaultTransactionDefinition def = new DefaultTransactionDefinition();

// 设置事务传播方式
def.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED);

// 从事物管理器中获取TransactionManager, 并获取TransactionStatus
TransactionStatus status = TransactionManagerHolder.getTransactionManager().getTransaction(def)

try {
    // TODO: 执行业务代码
    
    // 提交当前事务
    TransactionManagerHolder.getTransactionManager().commit(status);

} catch(Exception e) {
     TransactionManagerHolder.getTransactionManager().rollback(status);
}

多数据库源

框架中支持多数据源配置, 默认系统会加载master数据源,可以通过@DataSource方法注解 或者 DynamicDataSourceManager.setDataSourceCode(String) 来进行多数据源的切换, 这种方式也可以在数据库读写分离的场景使用。

application.yml

master: #主数据库配置
 db:
  type: mysql
  url: jdbc:mysql://192.168.0.1:3306/sgp_agent?useUnicode=true&characterEncoding=UTF-8&generateSimpleParameterMetadata=true&serverTimezone=Asia/Shanghai
  username: test
  password: ENC(9cOZdwpiByhh7bQcgNDXjNAfUKmujhB2)

gongcheng: #工程数据库配置
 db:
  type: oracle
  driverClass: oracle.jdbc.driver.OracleDriver
  url: jdbc:oracle:thin:@192.168.0.1:1521:CSORCL
  username: test
  password: ENC(9cOZdwpiByhh7bQcgNDXjNAfUKmujhB2)
  validationQuery: SELECT 1 FROM DUAL
 hibernate:
  dialect: org.hibernate.dialect.OracleDialect
  hbm2ddl:
    auto: none
  show_sql: false
  format_sql: false
  temp:
    use_jdbc_metadata_defaults: false

MaterialServiceImpl.java

@Override
@Transactional
@DataSource(DataSourcePrefixDef.GONG_CHENG)
public DataMaterialEntity getDataMaterialInfoFromGC(String stdCode, String materialCode) {
    return materialDao.getDataMaterialInfoFromGC(stdCode, materialCode);
}

不加注解默认为master数据源

DataOrderRecordService.java

@Transactional
void saveDataOrderRecord(DataOrderRecordEntity orderRecordEntity, String stdCode);

OrderController.java

@Override
	public String creatGCBill(@RequestBody Tcis2CrmOrderItemInput tcis2CrmOrderItemInput) {
		
           ......
           
		try {
			List<BillDetail> billDetails = Lists.newArrayList();
			if (CollectionUtils.isNotEmpty(tcis2CrmOrderItemInput.getCrmOrderGoods())) {
				tcis2CrmOrderItemInput.getCrmOrderGoods().stream()
						.filter(crmOrderGoods -> TcisCommonConstant.MATERIAL.equals(crmOrderGoods.getGoodsType()))
						.forEach(crmOrderGoods -> {
							DataMaterialEntity dataMaterialInfo = materialService.getDataMaterialInfoFromGC(stdCode, crmOrderGoods.getProductSn());
						 ......
						});
			}
			......
		} catch (Exception e) {
			DataOrderRecordEntity dataOrderRecordEntity = new DataOrderRecordEntity();
			.......
			dataOrderRecordService.saveDataOrderRecord(dataOrderRecordEntity, stdCode);
		}

		return gcBill.getExceedID();
	}

配置参数说明

1.简介

        Framework属于企业级底层开发框架,集成了log、cache、db、message、rule、tx,每块都以模块形式组织,可以根据项目需要获取模块。我们的初衷是屏蔽项目中各种第三方库之间的版本冲突,打造一套屏蔽底层中间件的全新API,提高项目代码的适配能力。

  • framework-common 定义公用的常量、工具类 采用了spring-boot方式启动, 启动类为Application, 也可以支持web方式启动。
  • framework-log 分布式集成日志模块,详细的记录了每个方法执行的参数、返回结果、执行时间,可以很方便的排查问题或告警,通过远程接口上传服务器(支持直连服务端,也支持通过kafka发送)
  • framework-cache 定义了缓存的获取。 支持注解方式访问缓存, 支持基于Redis的分布式锁
  • framework-db 是简单易用的轻量级DAO(Data Access Object)框架,它集成了Hibernate实体维护和Mybaits SQL分离的两大优势,提供了非入侵式API,可以与Hibernate、SpringJdbc等数据库框架很好的集成
  • framework-job 定时任务,支持quartz、xxl-job、ElasticJob简单封装的定时器,支持分布式、分片等功能
  • framework-message 消息模块,通过简单的api发布和订阅事件, 目前支持kafka、redis、rocketMq
  • framework-rule 规则引擎,基于json的轻量级规则引擎, 支持多种插件及扩展, 例如:基于状态机的工作流引擎
  • framework-tx 分布式事务,支持各种远程接口、同步异步消息。
  • [framework-dependencies] 项目依赖,解决版本包依赖问题
  • [framework-shell] 控制台方式提供命令操作,支持自定义各种命令,做各种小工具使用。
  • [framework-langchain4j] 对langchain4j的补充,支持国内的大模型,让大家更高效的开发AIGC应用。

jdk1.8请使用framework3.X版本,framework4.X已升级至jdk21版本

2.框架的由来

        Hibernate我用了2年半, 13年下半年去中兴软创用了一年SQL服务(软创内部框架), 14年在京东驻场用了2个月的MyBatis,综合了一下这些项目,各有各的优缺点。例如针对复杂业务SQL,hibernate明显能力不足,简单的功能MyBaties也要弄死人,所以一直在思考一个问题有没有一个框架能扬长避短,把大家的优点都发挥出来。 当时在某网站上看了一个帖子介绍了minidao,思路很新颖,拜读了源码。 从此框架之路走起。( 为什么不直接使用minidao,一是这个项目不火、更新节奏也不快,使用风险较大, 二是软创内部使用的都是自己的框架,连spring都没有,hibernate更不可能,jdk都处于1.4、1.5版本,不可能直接使用minidao )

  • 14年7月份左右在软创内部gitlab上发布了第一个版本easydao,主要是结合软创当时的系统框架在其之上封装了一层。
  • 14年10月份在github上发布了easydao 剥离掉软创内部框架依赖,使其可以不依赖软创的框架,可以结合spring和hibernate,或者可以单独使用jdbc来使用。
  • 15年6月开始framework-0.1版本的设计,数据库已经用的很爽了,但是一个项目不仅仅是数据库,还有很多其他东西, 当时针对的是web项目规划了很多模块,类似于现在的web结构, 做了job可以在线管理,消息、rpc、缓存等等功能
  • 16年1月22日正式发布1.0版本
  • 16年7月21日发布2.0版本,web模块和jeecg合并单独组成framework-manager, framework专门解决项目底层问题
  • 17年9月24日发布3.0版本,升级了spring boot版本至2.0, 去掉了dubbox这个rpc框架,引入spring-cloud框架。前端也放弃了jeecg,基于ant-design-pro 实现的一套web框架(目前还未从项目中分离出来,暂未开源)
  • 20年2月4日发布了3.4版本, 增加了framework-tx模块,正式支持分布式事务。
  • 23年人工智能比较火,又增加了framework-langchain4j,专门扩展国内的一些大模型。

3.采用项目

  1. 中兴视通网上营业厅项目V1.0
  2. 咪咕在线客服V1.0
  3. 中国实践教育平台V2.0
  4. 大丰科创园微信项目V1.0
  5. 苏州市总工会微信V1.0
  6. 佛山港华网上营业厅项目V1.0
  7. 苏州港华网上营业厅项目V1.0
  8. 苏州体育局微信活动运营项目V1.0
  9. 苏州市防汛排涝物资管理系统V1.0
  10. 港华集团网上营业厅项目
  11. E网通项目
  12. 港华紫荆微信项目
  13. 港华物联网平台
Clone this wiki locally