# sqltoy-quickstart **Repository Path**: sagacity/sqltoy-quickstart ## Basic Information - **Project Name**: sqltoy-quickstart - **Description**: 基于sqltoy的快速演示项目 - **Primary Language**: Java - **License**: Apache-2.0 - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 22 - **Forks**: 11 - **Created**: 2020-09-01 - **Last Updated**: 2025-02-11 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # sqltoy-orm 快速上手项目 * quickstart只演示了部分功能,核心是让大家快速上手,详细功能参见文档 * 理论上来sqltoy可以解决您项目上全部数据库交互,我们的erp、数据平台、电商平台已经验证了这一点 # 学习步骤 ## 1. 配置pom引入sqltoy的依赖 ```xml com.sagframe sagacity-sqltoy-spring-starter 5.6.39 ``` ## 2. 配置正确pom build避免sql文件无法编译到classes下面 * 核心配置:src/main/java 下面的**/*.xml ```xml src/main/java **/*.java **/*.xml src/main/resources **/*.xml **/*.properties **/*.yml **/*.sql **/*.jpg **/*.key src/test/java **/*.java **/*.xml src/test/resources **/*.xml **/*.properties **/*.yml **/*.sql **/*.key ``` ## 3. 配置application.yml,注意阅读配置上的备注 * 注意要点:sqlResourcesDir 是路径名,多个路径用逗号分隔,不要填错 * spring.sqltoy千万不要写成sqltoy漏掉spring开头 ``` #完整路径:spring.sqltoy spring: sqltoy: # 多个路径用逗号分隔(这里要注意是路径,sqltoy会自动向下寻找以sql.xml结尾的文件,不要写成classpath:com/**/*.sql.xml) sqlResourcesDir: classpath:com/sqltoy/quickstart # 默认值为classpath:sqltoy-translate.xml,一致则可以不用设置 translateConfig: classpath:sqltoy-translate.xml # 默认开启跨数据库函数自动适配(如oracle的nvl,当数据库切到mysql时会自动替换成ifnull) #functionConverts: default # 默认为false,debug模式将打印执行sql,并自动检测sql文件更新并重新加载 debug: true # 提供统一字段:createBy createTime updateBy updateTime 等字段补漏性(为空时)赋值(可选配置) unifyFieldsHandler: com.sqltoy.plugins.SqlToyUnifyFieldsHandler # sql执行超过多长时间则进行日志输出,用于监控哪些慢sql(可选配置:默认30秒) printSqlTimeoutMillis: 300000 # 数据库保留字兼容处理(原则上不要使用数据库保留字,多个用逗号分隔) #reservedWords: maxvalue,minvalue ``` * 最简单配置(注意:spring.sqltoy开头) ``` #完整路径:spring.sqltoy spring: sqltoy: # 多个路径用逗号分隔(注意这里填路径、路径!会自动相信寻找) sqlResourcesDir: classpath:com/sqltoy/quickstart ``` * properties 模式 ``` # sqltoy config spring.sqltoy.sqlResourcesDir=classpath:com/sqltoy/quickstart # 默认配置:classpath:sqltoy-translate.xml;classpath:translates,可以多个.trans.xml 放于classpath:translates目录下面 spring.sqltoy.translateConfig=classpath:sqltoy-translate.xml # 默认关闭函数替换 #spring.sqltoy.functionConverts=default # 是否开启debug模式,在开发阶段建议为true,会打印sql spring.sqltoy.debug=true #项目中用到的数据库保留字定义,这里是举例,正常情况下不用定义 #spring.sqltoy.reservedWords=status,sex_type # 设置获取数据源的策略实现类,只在多数据源场景下需要设置,DefaultDataSourceSelector是默认实现 #dataSourceSelector: org.sagacity.sqltoy.plugins.datasource.impl.DefaultDataSourceSelector #spring.sqltoy.defaultDataSource=dataSource spring.sqltoy.unifyFieldsHandler=com.sqltoy.plugins.SqlToyUnifyFieldsHandler #spring.sqltoy.printSqlTimeoutMillis=200000 ``` ## 4. 编写springboot 主程序,注意@ComponentScan配置 * 参见:src/main/java 下面的SqlToyApplication ```java package com.sqltoy; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.context.annotation.ComponentScan; import org.springframework.transaction.annotation.EnableTransactionManagement; /** * * @project sqltoy-quickstart * @description quickstart 主程序入口 * @author zhongxuchen * @version v1.0, Date:2020年7月17日 * @modify 2020年7月17日,修改说明 */ @SpringBootApplication @ComponentScan(basePackages = { "com.sqltoy.config", "com.sqltoy.quickstart" }) @EnableTransactionManagement public class SqlToyApplication { /** * @param args */ public static void main(String[] args) { SpringApplication.run(SqlToyApplication.class, args); } } ``` ## 5. 初始化数据库 * 参见src/test/java 下面的InitDataBaseTest,生成数据库表结构和初始化数据 ```java @ExtendWith(SpringExtension.class) @SpringBootTest(classes = SqlToyApplication.class) public class InitDataBaseTest { @Autowired private InitDBService initDBService; @Test public void testInitDB() { String dbSqlFile = "classpath:mock/quickstart_init.sql"; System.err.println("开始执行数据库初始化!"); initDBService.initDatabase(dbSqlFile); } } ``` ## 6. 利用quickvo生产VO(或POJO) * 在出问题时关注dataSource中的schema、catalog配置,其他问题请参见quickvo.xml中的注释 * pom.xml中增加quickvo的maven插件 ```xml com.sagframe quickvo-maven-plugin 1.0.5 ./src/main/resources/quickvo.xml ${project.basedir} com.mysql mysql-connector-j ${mysql.version} ``` * 配置src/main/resources/quickvo.xml 中的任务,关键部分如下 ```xml ``` * 点击mvn-quickvo.bat 即可生产VO了 * windows环境下: ``` mvn quickvo:quickvo ``` # 源码导航 * 阅读的入口 src/test/java com.sqltoy.quickstart * InitDataBaseTest 数据库初始化测试调用 * StaffInfoServiceTest 演示常规的CRUD * TreeTableTest 演示树形表结构的构建和查询 * ShardingSearchTest 演示分表记录保存和查询(Sharding策略请参见src/main/java com.sqltoy.config.ShardingStrategyConfig ) * AdvanceQueryTest 查询相关的演示 * UniqueCaseTest 演示唯一性验证 * CascadeCaseTest 演示级联操作 * LockCaseTest 演示锁记录修改操作 * StoreTest 演示存储过程调用 * JavaCodeSqlTest 演示在代码中写sql实现原本xml中的功能 * DTOConvertPOJOTest 演示在严格分层场景下DTO和POJO互转的范例 * LinkOptCaseTest 演示链式操作 * EntityOptsCaseTest 基于POJO类型的单表操作演示 * JsonTypeCaseTest 演示json等特殊类型的支持 * TransLedgerConcurrentTest 演示类似订单交易台账高并发事务处理 * ExecuteSql 演示executeSql和batchUpdate 执行sql更新或批量更新操作 * SecureEncryptDecryptTest 演示字段加解密功能 # 疑问解答 ## 为什么要将*.sql.xml 放在java路径下? * sqltoy推荐大家项目按照业务划分先分模块(消息中心、系统管理、订单管理等)后分层(web层、service),sql文件放于模块中便于模块整体迁移和产品化 * 有利于开发过程,一般项目按模块分工,让开发者不需要不断的切换目录 * 当然这个是sqltoy推荐做法,开发者则可以根据自身实际情况而定,并非强制! ## 为什么缓存不用redis? * 这里的缓存主要用于频繁的结果字段翻译,一旦你用了缓存翻译后你就会发现会在极为广泛的范围内使用,只有本地内存级缓存才能经得起频繁任意反复的调用,没有必要再来一个redis的IO消耗 * 你可以通过扩展实现ehcache+redis模式,来提升缓存的刷新实时性,但代价比较大造成了项目的复杂性,sqltoy提供的增量更新模式基本可以控制秒级更新 ## 为什么quickvo任务不是一个,而是按模块分多个任务 * sqltoy强调项目模块化,便于开发提炼出相对产品化的功能模块,逐步减少每次项目重复性工作,让不通过业务代码集中于一个模块下便于模块成熟后的抽离 ## 如何分VO和POJO * 请参见sqltoy-strict项目 https://github.com/sagframe/sqltoy-strict ## 如何分库分表 * 在本项目里面已经演示了分表 * 分库分表含事务型的范例: https://github.com/sagframe/sqltoy-showcase/tree/master/trunk/sqltoy-sharding ## 为什么dao不采用mybatis plus的接口模式? * mybatis dao采用接口模式,是其向jpa方向靠拢的一种模式,而sqltoy本身就是jpa+查询模式,也就是说jpa向查询方向加强,正好相反! * 什么接口?能够用接口来完成就是意味着可以用一个通用方法来代替!因此接口式dao的存在必要性就值得商榷! ```java @Service("organInfoService") public class OrganInfoServiceImpl implements OrganInfoService { //sqltoyLazyDao 就可以代替接口式的dao @Autowired SqlToyLazyDao sqlToyLazyDao; @Transactional public void saveOrganInfo(OrganInfoVO organInfoVO) { // 先保存机构 sqlToyLazyDao.saveOrUpdate(organInfoVO); // 设置树形表的节点路径等字段值,便于统一树形查询 // id字段根据vo找表的主键会自动匹配上,其它的NODE_ROUTE\NODE_LEVEL\IS_LEAF 为标准命名无需额外设置 //idField 如果是主键则无需设置 sqlToyLazyDao.wrapTreeTableRoute(new TreeTableModel(organInfoVO).pidField("organPid")); } } ``` * 考虑一些场景下dao仍然要做一些数据的封装处理(简化service层,将service尽量体现业务逻辑,减少一些dao的数据组装干扰),sqltoy仍然可以写dao,但dao时实体类! 如下:实体类又有何不妥呢!清晰又可以针对一些特殊情况自己完善一些小处理,mybatis那种接口通过aop方式谈不上什么酷和高技术,不要被带到沟里去了,清晰、可维护、好拓展才是正道! ```java @Repository("staffInfoDao") public class StaffInfoDao extends SqlToyDaoSupport { /** * @TODO 提供一个分页并动态设置缓存翻译的演示 * @param pageModel * @param staffInfoVO * @return */ public Page findStaff(Page pageModel, StaffInfoVO staffInfoVO) { // sql可以直接在代码中编写,复杂sql建议在xml中定义 // 单表entity查询场景下sql字段可以写成java类的属性名称 // 单表查询一般适用于接口内部查询 String sql = "#[staffName like :staffName]#[and createTime>=:beginDate]#[and createTime<=:endDate]"; return findPageEntity(pageModel,StaffInfoVO.class, EntityQuery.create().where(sql).values(staffInfoVO) // 字典缓存必须要设置cacheType // 单表对象查询需设置keyColumn构成select keyColumn as column模式 .translates(new Translate("dictKeyName").setColumn("sexTypeName").setCacheType("SEX_TYPE") .setKeyColumn("sexType")) .translates(new Translate("organIdName").setColumn("organName").setKeyColumn("organId"))); } ``` ## 多数据源怎么弄? * sqltoy可以配置默认数据源 ```properties spring.sqltoy.defaultDataSource=dataSourceName ``` * 如果是同类单据根据特定规则分多个库,请参见分库策略进行 * 通过多个lazyDao模式 ```java @Bean(name = "sqlToySkylineDao") public SqlToyLazyDao sqlToySkylineDao(@Qualifier("dataSourceSkyline") DataSource dataSource){ SqlToyLazyDaoImpl dao = new SqlToyLazyDaoImpl(); dao.setDataSource(dataSource); return dao; } @Bean(name = "sqlToyLazyDao") public SqlToyLazyDao sqlToyLazyDao(@Qualifier("dataSource") DataSource dataSource) { SqlToyLazyDaoImpl dao = new SqlToyLazyDaoImpl(); dao.setDataSource(dataSource); return dao; } ``` * 通过lazyDao里面调用时指定dataSource,save、update、load等都有链式操作 ```java sqlToyLazyDao.save().dataSource(dataSource).saveMode(SaveMode.IGNORE).many(entities); sqlToyLazyDao.query().sql("qstart_fastPage").dataSource(dataSource).entity(staffVO).findPage(pageModel); ``` ## 我想通过包路径来实现不同数据库访问 * 请扩展实现org.sagacity.sqltoy.plugins.datasource.DataSourceSelector 接口 * 当前默认实现(你可以通过aop+ThreadLocal来修改实现) ```java public class DefaultDataSourceSelector implements DataSourceSelector { @Override public DataSource getDataSource(ApplicationContext applicationContext, DataSource pointDataSouce, String sqlDataSourceName, DataSource injectDataSource, DataSource defaultDataSource) { // 第一优先:直接指定的数据源不为空 if (pointDataSouce != null) { return pointDataSouce; } DataSource result = null; // 第二优先:sql中指定的数据源 if (StringUtil.isNotBlank(sqlDataSourceName)) { result = getDataSourceBean(applicationContext, sqlDataSourceName); } // 第三优先:dao中autowired注入的数据源 if (result == null) { result = injectDataSource; } // 第四优先:sqltoy 统一设置的默认数据源 if (result == null) { result = defaultDataSource; } // 如果项目中只定义了唯一的数据源,则直接使用 if (result == null) { Map dataSources = applicationContext.getBeansOfType(DataSource.class); // 只有一个dataSource,直接使用 if (dataSources.size() == 1) { result = dataSources.values().iterator().next(); } } return result; } } ``` ## sqltoy传参支持map吗? * sqltoy传参可以三种 ```java /** * @todo 通过对象传参数,简化paramName[],paramValue[] 模式传参 * @param * @param sqlOrNamedSql 可以是具体sql也可以是对应xml中的sqlId * @param entity 通过对象传参数,并按对象类型返回结果 * @return */ public List findBySql(final String sqlOrNamedSql, final T entity); /** * @todo 通过给定sql、sql中的参数名、参数的数值以及返回结果的对象类型进行条件查询 */ public List findBySql(final String sqlOrSqlId, final String[] paramsNamed, final Object[] paramsValue, final Class voClass); /** * @todo 通过map传参 */ public List findBySql(final String sqlOrSqlId, final Map paramsMap, final Class voClass); ``` ## sqltoy必须返回VO吗? * sqltoy返回结果可以是VO、map、二维List,List ```java /** * @todo 通过给定sql、sql中的参数、参数的数值以及返回结果的对象类型进行条件查询 * @param sqlOrSqlId * @param paramsNamed 如果sql是select * from table where xxx=? * 问号传参模式,paramNamed设置为null * @param paramsValue 对应Named参数的值 * @param voClass 返回结果List中的对象类型(可以是VO、null:表示返回List;HashMap.class,Array.class 返回List List findBySql(final String sqlOrSqlId, final String[] paramsNamed, final Object[] paramsValue, final Class voClass); ``` ## sqltoy 的sql必须写在xml中吗? * sqltoy强调复杂sql放于xml中,但不限制您,如下面的代码第一个参数是sql或者sqlId,你可以直接传sql语句 ```java public List findBySql(final String sqlOrSqlId, final String[] paramsNamed, final Object[] paramsValue, final Class voClass); ``` ## 还有?? * 请阅读sqltoy下面的word文档说明