Closed cbqqkcel closed 1 year ago
好像还是Clob的问题。
Define dao = Define.dao;
List
return new JsonArray(list).encode();
lealone orm 框架是不会读写远程数据库的哦,只会读写嵌入式的数据库。
把那个 jdbc_url 改成"jdbc:lealone:embed:demo?user=root"
有可能是依赖太旧了,我重新搞一遍试试
是https://oss.sonatype.org/content/repositories/snapshots上面的代码太旧了导致的。我用lealone源码install后就可以了
为啥返回的字段都是数据库字段(大写带下划线的)。能返回 java 类的属性名吗。
因为数据库不区分大小写,所以默认都是大写带下划线,转成 json 时就是用字段名了
能设置吗,我这个是迁移到 lealone,前端都是陀峰的几乎没法改了(有多个端)。
这个可以改进,目前还没有设置开关。
Clob 字段还是有问题。刚才下载最先代码后以为可以了。实际上是重新启动后就不行了。并且我改成了嵌入式也不行。
可以用varchar(max)代替clob吗,这个字段是存json数据的。
可以
AllModelPropertyTest 这个是通过 orm 框架测试所有字段类型的例子,你跑跑看,把里面的 clob 字段用你的数据写入。 我重现不了问题。
setInMemory(true); setInMemory(false); 这两种场景你都跑一下看看。
能设置吗,我这个是迁移到 lealone,前端都是陀峰的几乎没法改了(有多个端)。
可以了,第一种全局方案,在建表时加 parameters(case_format='camel') case_format 支持 camel、lower_underscore、upper_underscore
create table test(property_name1 int, property_name2 long)
parameters(case_format='camel')
package 'my.model'
generate code 'src/main/java'
也可以单独调用 model 对象的 encode 和 decode 传递不同的 CaseFormat
User user = new User();
String json = user.encode(CaseFormat.CAMEL);
user = User.decode(CaseFormat.CAMEL);
详细的例子请看 OrmJsonTest
1.json返回的数据会多一个modelType 2.可以自定义全局序列化吗
3.还有希望字段可以支持json格式,目前spring boot + mybatis-plus是在代码里面用mybatis自定义type handler 把varchar字符串转换成json对象的。
前端和后端对接还是有很多麻烦事的。
自定义encode/decode是可以做到,但是你就得了解框架的实现了,而且 orm 框架不光只是在 model 和 json 之间转换,还要兼容跟 jdbc 客户端之间的数据协议。如果有这么复杂的需求,是不是可以考虑拿到 model 对象时自己遍历它的字段然后生成 json 就好了?
lealone orm 所有字段类型都对应一个 ModelProperty 的子类,像 mybatis 那样每种类型都对应一个 type handler 很容易实现,但是这种方案最大的缺点就是你得了解 orm 的内部实现原理才能用得好。
加了自定义encode/decode后,你还得通过另外的方式告诉前端数据的格式。
后续倒是可以考虑在定义字段或方法参数时给一些额外的约束或格式声明反而更容易一些,数据库本身就有类似的东西,只是因为数据库最开始不是为json准备的,前端的限制太多,连long都处理不了。
数据库定义一个字段,比如 f1 int,后面还跟了一堆东西的,只是大多数场景只用到字段名和类型,所以你说的那种格式相关的,最好的办法还是在定义字段时声明,这样你把 sql 写的接口规范发给使用方他们就看得懂了,你自己扩展 orm 框架最后还得通过其他方式告知别人数据的格式。
前端问题一大堆都2022年了还是要搞定兼容问题(一般都是h5的问题)后端管理页面还好升级一下浏览器就行。手机上没法升级或者用户不愿意升级。 lealone把后端搞简单了,导致前端问题反而更突出了。
复杂的前端需求我们就直接跳过 orm 框架,然后直接写 sql,前端要什么格式就在 select 表达式里用数据库自带的函数转换就好了。lealone orm 现在的实现也就2000多行代码,肯定没有很完善,也还不能用数据库内部的函数。
其实这些问题,对新项目问题不大。我们从后端拿到的数据不满足需要会在前端转换一下的。反正在用户端不会影响服务器计算压力。 数据库的函数我们几乎不用。用了mybatis-plus后整个中台项目除了报表很少写sql的。
你面对的问题是,前端已经成型了,数据格式定了,当切换到一个新的 orm 发现它的默认行为跟你的前端不匹配时,就得寻找一种扩展机制。就比如 boolean 类型为什么没有返回true和false,那是因为传0和1,数据能传少一点。
当然,框架要提供一些扩展机制肯定是必须的,lealone orm 加个encode/decode接口很容易,有深度定制的需求后续就用它就好了。
mybatis-plus 跟 lealone orm 的差别还是很大的,lealone orm 不会做成一个通用的 orm,只会为 lealone 服务。
你面对的问题是,前端已经成型了,数据格式定了,当切换到一个新的 orm 发现它的默认行为跟你的前端不匹配时,就得寻找一种扩展机制。就比如 boolean 类型为什么没有返回true和false,那是因为传0和1,数据能传少一点。
当然,框架要提供一些扩展机制肯定是必须的,lealone orm 加个encode/decode接口很容易,有深度定制的需求后续就用它就好了。
数据多一点问题不大的,一般查询都有分页,0和1或true,false,以及json多余的字段几乎是没有影响的方便开发和调试反而很重要。而且性能也不会出在查询上。http有gzip压缩相同的数据越多压缩比率越高影响几乎为0了
真的是大数据会用数组解决的。
lealone orm 的设计目标就是把学习成本降低,不要再像现在的 orm 一样需要学习一堆东西,只要建表生成 model 类,然后就能像使用 sql 一样操作数据库,还必须是类型安全的,又能方便 ide 重构代码。现在的问题大多是 model 转 json 时默认的数据格式不满足需求而已。
只是想用什么更容易使用的办法解决,加扩展接口虽然是最容易实现的,但用起来的学习成本是最高的。 mybatis 的源代码我也完整研究过几遍,mybatis-plus 的代码没有研究,lealone orm 一定不会走 mybatis 的路,mybatis-plus 的文档我看了一下,学习成本比 lealone orm 高多了,而且我看它给的例子,查询语句居然用字符串的字段名,这在 lealone orm 里是不允许的,修改字段名时你得搜索所有代码才知道有没有改完。
lealone orm 的设计目标就是把学习成本降低,不要再像现在的 orm 一样需要学习一堆东西,只要建表生成 model 类,然后就能像使用 sql 一样操作数据库,还必须是类型安全的,又能方便 ide 重构代码。现在的问题大多是 model 转 json 时默认的数据格式不满足需求而已。
只是想用什么更容易使用的办法解决,加扩展接口虽然是最容易实现的,但用起来的学习成本是最高的。 mybatis 的源代码我也完整研究过几遍,mybatis-plus 的代码没有研究,lealone orm 一定不会走 mybatis 的路,mybatis-plus 的文档我看了一下,学习成本比 lealone orm 高多了,而且我看它给的例子,查询语句居然用字符串的字段名,这在 lealone orm 里是不允许的,修改字段名时你得搜索所有代码才知道有没有改完。
mybatis-plus 也支持链式调用的不支持join。用它都是单表查询然后服务端用代码做join减少数据库压力。可以不用字符串的。
LambdaQueryWrapper<User> where = new LambdaQueryWrapper<>();
UserPageCondition cond = (UserPageCondition) page.getCondition();
if (cond != null) {
where.eq(cond.getStatus() != null, User::getStatus, cond.getStatus());
where.eq(cond.getGender() != null, User::getGender, cond.getGender());
where.like(StrUtil.isNotBlank(cond.getEmail()), User::getEmail, cond.getEmail());
where.like(StrUtil.isNotBlank(cond.getMobile()), User::getMobile, cond.getMobile());
where.like(StrUtil.isNotBlank(cond.getNickname()), User::getNickname, cond.getNickname());
Date[] createAts = cond.getCreateAts();
if (createAts != null && createAts.length == 2) {
where.between(User::getCreateAt, createAts[0], createAts[1]);
}
where.in(CollUtil.isNotEmpty(cond.getIds()), User::getId, cond.getIds());
where.notIn(CollUtil.isNotEmpty(cond.getNotIds()), User::getId, cond.getNotIds());
where.exists(StrUtil.isNotBlank(cond.getExistsSql()), cond.getExistsSql());
}
String q = page.getQ();
where.and(StrUtil.isNotBlank(q), item -> item.like(User::getName, q).or().like(User::getUsername, q));
lealone orm 早期的实现是直接基于 Jackson 的序列化和反序化注解来实现的,所以最开始就有扩展接口,但是 Jackson 反反复复出现安全漏洞,老是要升级,升烦了,因为序列化和反序化这个过程最容易出现安全漏洞,fastjson 也是如此。后来我就把序列化和反序化注解方案删除了,自己实现序列化和反序化,这就是为什么现在 lealone orm 只需要依赖 jackson-core 的原因。
加扩展接口让用户自己实现序列化和反序化,其实就是把安全的口子打开了,一不小心就会被黑客利用。
lealone orm 的设计目标就是把学习成本降低,不要再像现在的 orm 一样需要学习一堆东西,只要建表生成 model 类,然后就能像使用 sql 一样操作数据库,还必须是类型安全的,又能方便 ide 重构代码。现在的问题大多是 model 转 json 时默认的数据格式不满足需求而已。 只是想用什么更容易使用的办法解决,加扩展接口虽然是最容易实现的,但用起来的学习成本是最高的。 mybatis 的源代码我也完整研究过几遍,mybatis-plus 的代码没有研究,lealone orm 一定不会走 mybatis 的路,mybatis-plus 的文档我看了一下,学习成本比 lealone orm 高多了,而且我看它给的例子,查询语句居然用字符串的字段名,这在 lealone orm 里是不允许的,修改字段名时你得搜索所有代码才知道有没有改完。
mybatis-plus 也支持链式调用的不支持join。用它都是单表查询然后服务端用代码做join减少数据库压力。可以不用字符串的。
LambdaQueryWrapper<User> where = new LambdaQueryWrapper<>(); UserPageCondition cond = (UserPageCondition) page.getCondition(); if (cond != null) { where.eq(cond.getStatus() != null, User::getStatus, cond.getStatus()); where.eq(cond.getGender() != null, User::getGender, cond.getGender()); where.like(StrUtil.isNotBlank(cond.getEmail()), User::getEmail, cond.getEmail()); where.like(StrUtil.isNotBlank(cond.getMobile()), User::getMobile, cond.getMobile()); where.like(StrUtil.isNotBlank(cond.getNickname()), User::getNickname, cond.getNickname()); Date[] createAts = cond.getCreateAts(); if (createAts != null && createAts.length == 2) { where.between(User::getCreateAt, createAts[0], createAts[1]); } where.in(CollUtil.isNotEmpty(cond.getIds()), User::getId, cond.getIds()); where.notIn(CollUtil.isNotEmpty(cond.getNotIds()), User::getId, cond.getNotIds()); where.exists(StrUtil.isNotBlank(cond.getExistsSql()), cond.getExistsSql()); } String q = page.getQ(); where.and(StrUtil.isNotBlank(q), item -> item.like(User::getName, q).or().like(User::getUsername, q));
这种代码太复杂了,我一个搞数据库的人光看这种代码都不知道这段代码对应的 sql 是什么样子。 好的 orm 应该是用 sql 的知识也能用 java 代码描述出来。
@qqcbqqkcel 5.0.1-SNAPSHOT create table 的参数 caseFormat 已更改为 case_format
在 sql 语句中使用的参数名统一用下划线,不区分大小写。
建议将boolean类型返回true和false理由如下 1.不会丢失类型语义更清晰,在js前端开发时boolean类型字段和其他number类型值为0时混在一起不好区分 2.由于丢失类数据类型,无法只通过json数据知道数据类型。 3.一般boolean字段也不太多加上gzip压缩并不会增加多上网络带宽。 4.增加了前端的存储空间,在js中 number类型最少都是8个字节哪怕是0和1。 5.前端除了浏览器还有安卓、iOS、其他设备等,安卓是java int类型4个字节。 6.各前端可能对int类型处理不一样,如是安卓java用int类型做比较和boolean做比较,很显然是boolean类型方便清晰。 7.0和1会导致各端出现不一样的处理
lealone orm 框架已经在设计扩展机制了,允许用户定制 model 到 json 所有的编解码操作,包括 boolean 类型。
返回给前端 0 和 1 主要是设计时想统一字段返回给前端、jdbc 客户端以及持久化到硬盘的数据格式。 后两者存储传输 boolean 类型都只需要1个字节。
新版本默认会提供几套编解码方案,比如针对前端的使用习惯也会有单独的一套,还不满足需求的话通过扩展接口定制就好了。
@qqcbqqkcel 如果你的需求很急,自己修改一下 org.lealone.plugins.orm.property.PBoolean 把前面几个方法改成这样,然后自己构建一下就可以了:
public PBoolean(String name, M model) {
super(name, model);
}
@Override
protected Value createValue(Boolean value) {
return ValueBoolean.get(value);
}
@Override
protected Object encodeValue() {
return value;
}
@Override
protected void deserialize(Value v) {
value = v.getBoolean();
}
@Override
protected void deserialize(Object v) {
value = (Boolean) v;
}
@qqcbqqkcel
lealone orm 框架自定义 json 数据格式的代码已经提交,你可以更新一下 lealone 和 lealone plugins 的代码。
lealone orm 框架现在默认的 json 数据格式是 FRONTEND_FORMAT,boolean 类型返回 true 和 false,字段名是驼峰格式。 如果还不满足需求,你可以写一个类继承 FrontendJsonFormat ,盖它的方法实现, 然后在建表或建数据库时加上 parameters(json_format='xxx.MyJsonFormat') ,这样就会使用你自己定义的数据格式。
目前自定义 json 数据格式只是一个实验特性,有什么问题可以随时发出来。
lealone orm 的设计目标就是把学习成本降低,不要再像现在的 orm 一样需要学习一堆东西,只要建表生成 model 类,然后就能像使用 sql 一样操作数据库,还必须是类型安全的,又能方便 ide 重构代码。现在的问题大多是 model 转 json 时默认的数据格式不满足需求而已。 只是想用什么更容易使用的办法解决,加扩展接口虽然是最容易实现的,但用起来的学习成本是最高的。 mybatis 的源代码我也完整研究过几遍,mybatis-plus 的代码没有研究,lealone orm 一定不会走 mybatis 的路,mybatis-plus 的文档我看了一下,学习成本比 lealone orm 高多了,而且我看它给的例子,查询语句居然用字符串的字段名,这在 lealone orm 里是不允许的,修改字段名时你得搜索所有代码才知道有没有改完。
mybatis-plus 也支持链式调用的不支持join。用它都是单表查询然后服务端用代码做join减少数据库压力。可以不用字符串的。
LambdaQueryWrapper<User> where = new LambdaQueryWrapper<>(); UserPageCondition cond = (UserPageCondition) page.getCondition(); if (cond != null) { where.eq(cond.getStatus() != null, User::getStatus, cond.getStatus()); where.eq(cond.getGender() != null, User::getGender, cond.getGender()); where.like(StrUtil.isNotBlank(cond.getEmail()), User::getEmail, cond.getEmail()); where.like(StrUtil.isNotBlank(cond.getMobile()), User::getMobile, cond.getMobile()); where.like(StrUtil.isNotBlank(cond.getNickname()), User::getNickname, cond.getNickname()); Date[] createAts = cond.getCreateAts(); if (createAts != null && createAts.length == 2) { where.between(User::getCreateAt, createAts[0], createAts[1]); } where.in(CollUtil.isNotEmpty(cond.getIds()), User::getId, cond.getIds()); where.notIn(CollUtil.isNotEmpty(cond.getNotIds()), User::getId, cond.getNotIds()); where.exists(StrUtil.isNotBlank(cond.getExistsSql()), cond.getExistsSql()); } String q = page.getQ(); where.and(StrUtil.isNotBlank(q), item -> item.like(User::getName, q).or().like(User::getUsername, q));
这种代码太复杂了,我一个搞数据库的人光看这种代码都不知道这段代码对应的 sql 是什么样子。 好的 orm 应该是用 sql 的知识也能用 java 代码描述出来。
大佬你看看为啥我用 leanlone-orm 写起来比 mybatis-plus 还复杂啊。 是用的方式不对吗
public interface UserService {
default Page<String> page(Page<UserPageCond> page) {
User dao = User.dao;
User countDao = User.dao;
UserPageCond cond = page.getCond();
if (cond != null) {
if (StrUtil.isNotBlank(cond.getStatus())) {
dao = dao.status.eq(cond.getStatus());
countDao = countDao.status.eq(cond.getStatus());
}
if (StrUtil.isNotBlank(cond.getGender())) {
dao = dao.gender.eq(cond.getGender());
countDao = countDao.gender.eq(cond.getGender());
}
if (StrUtil.isNotBlank(cond.getEmail())) {
dao = dao.email.like(cond.getEmail());
countDao = countDao.email.like(cond.getEmail());
}
if (StrUtil.isNotBlank(cond.getMobile())) {
dao = dao.mobile.like(cond.getMobile());
countDao = countDao.mobile.like(cond.getMobile());
}
if (StrUtil.isNotBlank(cond.getNickname())) {
dao = dao.nickname.like(cond.getNickname());
countDao = countDao.nickname.like(cond.getNickname());
}
Date[] createAts = cond.getCreateAts();
if (createAts != null && createAts.length == 2) {
Date createAtBegin = createAts[0];
Date createAtEnd = createAts[1];
dao = dao.createAt.between(createAtBegin, createAtEnd);
countDao = countDao.createAt.between(createAtBegin, createAtEnd);
}
if (CollUtil.isNotEmpty(cond.getIds())) {
dao = dao.id.in(cond.getIds());
countDao = countDao.id.in(cond.getIds());
}
if (CollUtil.isNotEmpty(cond.getIds())) {
dao = dao.id.notIn(cond.getIds());
countDao = countDao.id.notIn(cond.getIds());
}
}
String q = page.getQ();
dao = dao.and().name.contains(q).or().username.contains(q);
countDao = countDao.and().name.contains(q).or().username.contains(q);
long total = countDao.findCount();
if (total > 0) {
page.setTotal(total);
List<User> list = dao.offset(page.getPageOffset()).limit(page.getPageLimit()).findList();
List<String> result = new ArrayList<>();
for (User user : list) {
result.add(user.encode());
}
return Page.create(total, result);
} else {
return Page.empty();
}
}
}
先描述一下你想干嘛
countDao 那个不需要的,全都可以删了
一个普通的分页+多条件查询 有范围查询,如时间,字段不会存在 Model中 Model中有 创建日期,但是前端会传开始日期和结束日期: 有动态排序,排序字段是从前端传递过来的。
不用countDao怎么实现没有找到总记录就不执行数据查询呢
long total = dao.findCount();
if (total > 0) {
page.setTotal(total);
List<User> list = dao.findList();
List<String> result = new ArrayList<>();
for (User user : list) {
result.add(user.encode());
}
return Page.create(total, result);
} else {
return Page.empty();
}
上面的如果count只有一条也会把全部记录查询出来。查询条件会被清空
2023-06-06T23:23:44.320+08:00 INFO 7424 --- [nio-5220-exec-7] org.lealone.plugins.orm.Model : execute sql: SELECT
COUNT(*)
FROM PUBLIC."User"
/* PUBLIC."User".tableScan */
WHERE (PUBLIC.User."username" LIKE STRINGDECODE('%\u5f20\u56fd\u8d85%'))
OR ((PUBLIC.User."status" = 'NORMAL')
AND (PUBLIC.User."name" LIKE STRINGDECODE('%\u5f20\u56fd\u8d85%')))
2023-06-06T23:23:44.321+08:00 INFO 7424 --- [nio-5220-exec-7] org.lealone.plugins.orm.Model : execute sql: SELECT
User."id",
User."name",
User."username",
User."nickname",
User."avatar",
User."mobile",
User."email",
User."gender",
User."status",
User."createAt",
User."updateAt",
User."isDelete",
PUBLIC.User._ROWID_
FROM PUBLIC."User"
/* PUBLIC."User".tableScan */
我看一下,我记得 dao 是能复用的,可能哪里改动了。
把 Page<String>
改成 Page<User>
,后面就不用自己调用 encode 了。
findCount() 也是要查询记录的,lealone-orm 是嵌入式框架,findCount() 和 findList() 都要查数据,默认是有缓存的,执行两次有缓存的话也就相当于一次。
我现在用的是 spring boot,外面包了一层分页,返回的数据是这样的
{
total: 1000,
records: [
{name: 'dmy'}
]
}
用户Model没有get set方法现在还不能直接放到Page对象里面返回。需要写个扩展。所以现在是跑一下看看结果
where.eq(cond.getStatus() != null, User::getStatus, cond.getStatus()); mybatis-plus 的 cond.getStatus() != null 相当于 if 条件不成立就不会把查询条件加到sql中。
还有就是怎么动态排序
isNotBlank、isNotEmpty 这些写得的确啰嗦,如果 eq、like 这些操作提供 eqNotBlank、likeNotEmpty 应该就省事些。 你无非就是想如果查询条件不出现某个字段就不组装到 where 条件里。
动态排序就是在后面拼 orderBy().name.asc().phone.desc();
eq、like、asc、desc 都是操作符,如果提供 NotBlank、NotEmpty 版本,实现你想要的功能预计不到15行代码。
动态排序就是在后面拼 orderBy().name.asc().phone.desc();
if (ArrayUtil.isNotEmpty(page.getSorts())) {
String[] sorts = page.getSorts();
if (Objects.equals("name", sorts[0])) {
String sort = sorts[1];
if (Objects.equals("asc", sort)) {
dao.orderBy().name.asc();
} else {
dao.orderBy().name.desc();
}
} else if (Objects.equals("username", sorts[0])) {
String sort = sorts[1];
if (Objects.equals("asc", sort)) {
dao.orderBy().username.asc();
} else {
dao.orderBy().username.desc();
}
} else if (Objects.equals("status", sorts[0])) {
String sort = sorts[1];
if (Objects.equals("asc", sort)) {
dao.orderBy().status.asc();
} else {
dao.orderBy().status.desc();
}
} else if (Objects.equals("createAt", sorts[0])) {
String sort = sorts[1];
if (Objects.equals("asc", sort)) {
dao.orderBy().createAt.asc();
} else {
dao.orderBy().createAt.desc();
}
}
}
如果这样那排序会特别多,因为几乎每个字段都能排序,而且还有多字段排序
我之前设计的排序是数据结构是这样的,前端传过来 ['name', 'desc', 'username', 'asc', 'createAt', 'desc']
这个是这样的mybatis-plus排序非常简单。
String[] sorts = page.getSorts();
if (ArrayUtil.isNotEmpty(sorts)) {
if (sorts.length % 2 != 0) {
throw new BusinessException("排序字段不成对,必须为:['a', 'asc', 'b', 'desc']");
}
List<OrderItem> orderItems = new ArrayList(sorts.length / 2);
for(int i = 0; i < sorts.length; i += 2) {
orderItems.add(new OrderItem(sorts[i], sorts[i + 1].equals("asc")));
}
dto.setOrders(orderItems);
}
如果你想这么灵活的话,我建议你用反射,从 dao 里通过反射取出字段来调用。 这样你只要写一个通用的 crud 模板就能应对所有表了,如果你像现在这样每个表都这么拼,表多了之后怎么办,一个个去搞行不通的。
我不清楚应用传递给 mybatis-plus 的字符串字段它有没有办法确保这些字符串真的是字段,如果没有办法确保的话是很容易出现 sql 注入漏洞的。lealone orm 之所以不接受字符串字段就是为了安全。
我不清楚应用传递给 mybatis-plus 的字符串字段它有没有办法确保这些字符串真的是字段,如果没有办法确保的话是很容易出现 sql 注入漏洞的。lealone orm 之所以不接受字符串字段就是为了安全。
mybatis-plus会从 table meta 获取表的字段判断存不存在。
他后台的asc 和 desc是传一个 boolean值的里面 是否倒序
代码卡在17行