NutzCN Logo
问答 NutzDao如何使用fetchByJoin或者queryByJoin查询到与主entity映射为同一个数据表的关联对象(一对一或者一对多)
发布于 321天前 作者 微力无边 1069 次浏览 复制 上一个帖子 下一个帖子
标签:

比如用户实体UserEntity中有一个一对一的属性:上级领导private UserEntity leader,很明显这个上级领导也是映射到user表的,这样使用nutzdao的fetchByJoin查询用户a的user对象时:UserEntity a = dao.fetchByJoin("leader",id),就会报错:Ambiguous field........,这是因为nutzdao缺失对关联查询中的每个表设置一个自动生成别名的策略,这个例子中,从表和主表是同一个表,就会产生查询歧义,目前我只能先fetch后fetchLinks这样分2步处理,不能使用fetchByJoin一步完成查询。不知道有没有更好的办法,求教大神,跪谢~~~

另外,还有1个问题,我在这一并抛出来哈:
假如A一对一B,B一对一C,我这边的场景需求是这样的:查询A对象的分页列表数据(query),要求:1、同时查询到B对象和C对象;2:查询条件使用到了C的属性,目前我使用的是dao.queryByJoin(this.classOfT, regex, cnd, pager),但是这个方法中的regex只能是与A直接关联的B属性,无法查询到C,而且这个方法中的cnd也只能是针对A的属性或者B的属性来拼接,无法使用C的属性来参与查询条件的拼接,困惑啊。。。。。。。。。。。。。。。我目前的做法也是先查询A和B,再遍历结果集查询C,笨笨的做法。

88 回复

谢谢您!@wendal 这2个问题非常典型,之前也有人提过类似的问题,期待下次nutzDao升级后能提供响应的解决方案。

嗯嗯, 考虑一下

@wendal 您好!求教一个问题哈:目前queryByJoin(Class classOfT, String regex, Condition cnd) 和 fetchByJoin(Class classOfT, String regex, Condition cnd) 这2个方法中的cnd都是追加在最终的sql的where后面的,结果就是对主表数据进行了过滤,有时候我们往往只想对从表数据进行更加严格的选择性匹配,即希望能够在left join 的on后面追加条件,NutzDao默认在left join的时候,只做主表和从表的外键关联,比如: select a.* , b.name from a left join b on a.b_id = b.id . 如果能够在on后面追加条件, 那么查询结果就不会影响主数据的条数,只会影响从数据的匹配结果,即本例中b.name的值。期待下次nutzDao升级后能提供响应的解决方案。万谢!

上面3个问题是我目前使用NutzDao过程中遇见的3个最主要的开发者需求,期待大神来解决哈!

@wendal 非常渴望您的帮助!跪谢!

说真的,我觉得你可以考虑自定义sql的。。。

@qq_ee2dce55 能不能给出你期望得到的SQL呢? 我还是不太看懂你的需求

你觉得这样可行不?

<T> int queryByJoin(Class<T> classOfT, String regex, Map<String, Condition> cnds, pager);
<T> int countByJoin(Class<T> classOfT, String regex, Map<String, Condition> cnds);

@wendal 是这样的:目前queryByJoin和fetchByJoin中的Condition cnd最后都是拼接在sql的where后面的:select a.* , b.name from a left join b on a.b_id = b.id where cnd转化的条件,而我想要的是针对这2个方法能否在添加一个参数(比如Condition cndAfterOn),实际执行的sql是这样的:select a.* , b.name from a left join b on a.b_id = b.id and cnd转化的条件.

@wendal 是这样的:目前queryByJoin和fetchByJoin中的Condition cnd最后都是拼接在sql的where后面的:select a.* , b.name from a left join b on a.b_id = b.id where cnd转化的条件,而我想要的是针对这2个方法能否在添加一个参数(比如Condition cndAfterOn),实际执行的sql是这样的:select a.* , b.name from a left join b on a.b_id = b.id and cndAfterOn转化的条件.
上面那条回复中最后应该是“ and cndAfterOn转化的条件” 。

@wendal 也就是说很多时候主表和从表关联的条件不止是a.b_id = b.id 这样简单的外键,还会有其他的条件需要拼接在on 关键字后面,比如:select a.* , b.name from a left join b on a.b_id = b.id and b.is_delete = false.

所以我提出用Map来传一个Cnd映射表, key是关联属性名, 值是条件对象, 你觉得这个方案如何?

您好!@wendal queryByJoin和fetchByJoin接口参数中,如果能再添加一个参数就好了:这个参数决定把Condition对象渲染出来的条件语句放在 on后面还是where 后面。

额, 把形式写一下?

Nutz Dao目前已有以下3个泛型关联查询接口:
1、public T fetchByJoin(Class classOfT, String regex, Condition cnd)
2、public List queryByJoin(Class classOfT, String regex, Condition cnd)
3、public List queryByJoin(Class classOfT, String regex, Condition cnd, Pager pager)

对比上面3个接口,可否再新增这样的3个接口,如您上次回复的那样,第四个参数cnds中的key表示关联的属性(适用于@One、@Many、@ManyMany3种情况),cnds中的value表示left join on后面的关联条件,而第三个参数cnd则表示最终where语句的查询条件:
4、public T fetchByJoin(Class classOfT, String regex, Condition cnd, Map<String, Condition> cnds)
5、public List queryByJoin(Class classOfT, String regex, Condition cnd, Map<String, Condition> cnds)
6、public List queryByJoin(Class classOfT, String regex, Condition cnd, Map<String, Condition> cnds, Pager pager)

另外:由于有些表的个别column是大字段类型,所以query时无需查出来,急盼以下几个接口(Nuz Dao框架中目前没有),locked表示忽略少数字段
针对上面的123:
7、public List query(Class classOfT, Condition cnd,String locked)
8、public List queryByJoin(Class classOfT, String regex, Condition cnd,String locked)
9、public List queryByJoin(Class classOfT, String regex, Condition cnd, Pager pager,String locked)
针对上面的56:
10、public List queryByJoin(Class classOfT, String regex, Condition cnd, Map<String, Condition> cnds,String locked)
11、public List queryByJoin(Class classOfT, String regex, Condition cnd, Map<String, Condition> cnds, Pager pager,String locked)

果然还是要加map...

我也实在想不到,更好的形式了,但是项目中确实有很多这样的需求场景

我非常喜爱使用nutz,感谢大神哈!@wendal

我看看怎么改一下

这个String locked是针对关联对象的属性吧?

@wendal 不是的,locked是指主表的字段,因为locked是个字符串表达式,如果能从这个表达式中自动识别出主表和从表各自需要lock的字段,就完美了。

@wendal 当然前提是nutz dao认为主表和从表的lock字段名称不相同。

好像相同的话也没问题,都不查询出来。

那用FieldFilter应该就够了

关联查询时,FieldFilter对于从表数据也适用吗?

FieldFilter可以的,仔细看FieldFilter的api,可以注册多个类的过滤模式

@wendal 上面第4,5,6三个接口在哪个版本可以实现?

Map<String, Condition> cnds 已经加了,你可以拿最新快照看看

@wendal 目前Nutz Dao使用FieldMatcher的query接口只有这一个:
public List query(final Class classOfT, final Condition cnd, final Pager pager, FieldMatcher matcher)
这个方法可以兼容上面我列出的第7个,但是无法兼容8,9,10,11.

@wendal 因为8,9,10,11带有String regex参数。

额,我用的竟然是FieldMatcher...

嗯,不是FieldFilter。

我也不清楚FieldMatcher和FieldFilter有什么区别,我目前没使用过FieldMatcher,FieldFilter在 public int update(final Object obj, FieldFilter fieldFilter, final Condition cnd) 接口使用过。

@wendal 渴望Nutz dao未来能提供更加丰富的查询接口,就我个人使用情况看,这也是nutz dao比h和m用的最最最舒服的地方。因为insert、update、delete操作基本都是单表或者单记录操作,h、m或者spring data jpa 在连表查询方面做的最差,而且h把sql嵌入到java代码中,而m使用了让人讨厌的xml。

FieldFilter.locked(...).set(...).set(...).run(()->{
         dao.query(....);
})

@wendal 请问大神,nutz dao增加了类似上面8,9,10,11的接口了吗?
public List query(final Class classOfT, final Condition cnd, final Pager pager, FieldMatcher matcher)中,matcher对象又如何使用呢?
万谢!

用FieldFilter就行啦,不需要改接口

@wendal 惭愧啊,我不怎么熟悉jdk8 lambda语法
FieldFilter.locked(...).set(...).set(...).run(()->{
dao.query(....);
}),
可以翻译成java7的语法吗?

FieldFilter.locked(...).set(...).set(...).run(new Atom(){
    public void run(){
        dao.query(....);
    }
})

@wendal 大神,太谢谢您啦!!!!!
今天,我又遇见2个疑惑:
1、 Nutz Dao里所有带有分页参数Pager pager的接口(query或者queryByJoin),如果pager参数传入null,是不是就表示不分页,全部查询出来?
2、FieldMatcher类的这个方法 :public static FieldMatcher make(String actived, String locked, boolean ignoreNull) 中,第三个参数ignoreNull具体是指忽略什么值为null的数据?查询结果集吗?

  1. 是的, 不传就全部查, 如果页数是0, 也是全部查
  2. ignoreNull是给update用的, 传个false就行

@wendal 这个查询接口也用到了FieldMatcher,是否不建议使用这个接口了:
public List query(final Class classOfT, final Condition cnd, final Pager pager, FieldMatcher matcher)

@wendal update里用的是FieldFilter,不是FieldMatcher:public int update(final Object obj, FieldFilter fieldFilter)

其实嘛,FieldFilter就是FieldMatcher的集合

@wendal
FieldFilter.locked(...).set(...).set(...).run(new Atom(){
public void run(){
dao.query(....);
}
})
run方法里的query的参数如何传递进去呢?
dao.query(....);的查询结果又如何拿到呢?
我的基础方法是这样的:
public List queryByJoin(String regex,Condition cnd,String locked) {
//待测试
List records = null;
FieldFilter.locked(this.classOfT,locked).run(new Atom(){
public void run(){
// 这里的this.classOfT, regex, cnd都是取值上级方法,编译错,run方法不能识别。
// records 也不能识别。
records = dao.queryByJoin(this.classOfT, regex, cnd);
}
});
return copier.copyEntities(records,this.classOfDTO);
}

那是内部类,需要final变量

往外传值可以参考 htrp://wendal.net/404.html

@wendal 下面这个方法来自Nutz Dao
protected Object _fetchLinks(Object t, String regex, boolean visitOne, boolean visitMany, boolean visitManyMany, final Condition cnd, final Map<String, Condition> cnds) {
EntityOperator opt = _optBy(t);
if (null == opt)
return t;
if (visitMany)
opt.entity.visitMany(t, regex, doLinkQuery(opt, cnd, cnds));
if (visitManyMany)
opt.entity.visitManyMany(t, regex, doLinkQuery(opt, cnd, cnds));
if (visitOne)
opt.entity.visitOne(t, regex, doFetch(opt));
opt.exec();
return t;
}
我发现,一对多或者多对多时,用到了cnds这个参数,且条件是拼接在sql语句的where后面的,这2种情况的逻辑应该没有问题。
但是一对一关系时,实际开发需求也要用到 final Map<String, Condition> cnds这个参数,此时查询条件拼接每个关联的在 left join on后面,而不是where后面。就像下面这个sql,on a.b_id = b.id 是外键关联条件,而and b.is_delete = false就是我们传递的cnds之一。
select a.* , b.name from a left join b on a.b_id = b.id and b.is_delete = false.

无论一对一、一对多、多对多, final Condition cnd始终是拼接在主SQL的where后面的,而final Map<String, Condition> cnds 就要分情况了:
1、一对一时:nutz dao没有实现,查询条件拼接每个关联的在 left join on后面;
2、一对多或者多对多时:nutz dao已经实现,查询条件拼接每个子查询的where后面,doLinkQuery方法中的以下代码也验证了这一点:
if (_cnd == null && cnds != null)
cnd = cnds.get(lnk.getLinkedField().getName());

@wendal Nutz Dao最新的快照针对queryByJoin的情况已经添加了对cnds支持的接口:
public List queryByJoin(Class classOfT, String regex, Condition cnd, Pager pager, Map<String, Condition> cnds)
可不可以针对fetchByJoin的情况也新增一个支持cnds的接口:
public T fetchByJoin(Class classOfT, String regex, Condition cnd, final Map<String, Condition> cnds)

queryByJoin合适不?

fetch加cnds,应该可以

@wendal queryByJoin合适:
public List queryByJoin(Class classOfT, String regex, Condition cnd, Pager pager, Map<String, Condition> cnds)
方法参数很全面了,兼顾了主sql的cnd和分页的情况。

@wendal 无论一对一、一对多、多对多,我感觉Nutz dao在做queryByJoin或者fetchByJoin查询时,都是先查询主数据,然后再查询关联的从数据,并没有使用left join on的关联查询,这样会产生N+1的问题,mybatis貌似就存在那样的问题,一直没解决掉。
我认为起码一对一时queryByJoin或者fetchByJoin应该使用left join on 这样的关联查询,这样只需要执行1条sql,一对多的情况应该也可以这么做,多对多的情况,貌似只能分多个子查询sql来做了。

一对一的时候做了吧?应该有left join呀

一对多貌似做不了吧?你有方案?

多对多是肯定没戏的。。。

@wendal 一对一时Nutz dao应该实现了left join, 刚才是我记错了。
多对多是肯定没戏的。。。
一对多时,打个比方,主表有2条数据,这2个主数据关联的从表数据分别是3条和4条,那么使用 left join on查询时,结果集条数应该:
1*3+1*4 = 7
关键就是要从这个结果集中,解析出主数据和从数据,我觉得可以在select 的字段中,为从表数据的所有字段加一个前缀,比如从表表名+"_"+从表字段名,并按照关联的从表的主键排序,那么java代码在遍历这个结果集时,很容易区分出主表和从表的数据了。因为遍历的时候,如发现当前主表id与上一个不一样了,说明要new 一个主表数据,且从表数据new 一个list。。。。。。

@wendal 如果设计的巧妙,“并按照关联的从表的主键排序” 都不需要的。

@wendal 从表表名+"_"+从表字段名中,使用下划线来拼接也不是一个好的方案,可以改为其他不会产生歧义的符号,因为从表表名+"_"+从表字段名 有可能就和主表的某个字段同名了(典型的情况就是从表外键),要么就同时把select的主表的字段as为主表表名+"_"+主表字段名。

这样就应该不会产生歧义了

额,你设计个新的规则?

我建议你动手改一改,ok的话发个pull request

@wendal 大神,别误会啊,目前Nutz dao里具体使用了哪些规则,我也没深入看源码,刚才我只是针对如何使用1条sql来查询并解析1对多的情况,班门弄斧的提了我个人的解决思路哈。

@wendal 具体如何结合Nutz Dao现有的模式和规则来实现这个,臣妾做不到啊

没误会,我也想看看你的想法具体实现成什么效果,毕竟代码更有说服力,源码熟悉一下并没什么坏处嘛

都是可以探讨的事情,代码面前人人平等嘛😄

@wendal 我只能写下大概逻辑哦,我代码水平很次:

//代码功能示例
	public List<Org> queryByJoin() 
	{
		//假设Org和User是一对多的关系:一个部门有多个员工
		//假设Org类有一个属性: private List<User> users;
		//现查询Org时同时查询出其员工users
		//假设Nutz Dao已经通过注解等方式拼接出以下sql:
		String sql = " select org.org_id as org_org_id,org.name as org_name,user.user_id as user_user_id,user.name as user_name from org left join user on org.org_id = user.org_id order by org.org_id, user.user_name ";
		//查询
		Sql sql = Sqls.queryRecord(sql);
		dao.execute(sql);
		List<Record> records = sql.getList(Record.class);
		
		//返回封装好的结果集
		List<Org> orgs = new ArrayList<Org>();
		//解析主数据和从数据
		String orgIdTemp = null;//临时变量,假设数据库所有表主键是字符串类型
		String Org orgTemp = null;//临时变量
		for (Record re : list) {
			if(temp_org_id==null || !temp_org_id.equals(re.getString("org_org_id"))){//Nutz 可以轻松拿到主表的主键的column
				Org org = re.toEntity(dao.getEntity(Org.class), "org_");////Nutz 可以轻松拿到主表的表名
				orgs.add(org);
				orgs.setUsers(new ArrayList<User>());
				//让临时变量也指向这个org对象
				orgTemp = org;
			}
			//因为上面的sql已经按照主键排序了且上面的代码已经做了主数据的识别,所需这里需要往users里add user即可:
			User user = re.toEntity(dao.getEntity(User.class), "user_");//Nutz 可以轻松拿到从表的表名
			orgTemp.getUsers.add(user);
		}

		return orgs;

	}


@wendal 我修复一下刚才的代码:

//代码功能示例
	public List<Org> queryByJoin() 
	{
		//假设Org和User是一对多的关系:一个部门有多个员工
		//假设Org类有一个属性: private List<User> users;
		//现查询Org时同时查询出其员工users
		//假设Nutz Dao已经通过注解等方式拼接出以下sql:
		String sql = " select org.org_id as org_org_id,org.name as org_name,user.user_id as user_user_id,user.name as user_name from org left join user on org.org_id = user.org_id order by org.org_id, user.user_name ";
		//查询
		Sql sql = Sqls.queryRecord(sql);//Nutz Dao 已存在接口
		dao.execute(sql);//Nutz Dao 已存在接口
		List<Record> records = sql.getList(Record.class);//Nutz Dao 已存在接口
		
		//返回封装好的结果集
		List<Org> orgs = new ArrayList<Org>();
		//解析主数据和从数据
		String orgIdTemp = null;//临时变量,假设数据库所有表主键是字符串类型
		String Org orgTemp = null;//临时变量
		for (Record re : list) {
			//关键一步,识别主数据,保证主数据个数。
			if(temp_org_id==null || !orgIdTemp.equals(re.getString("org_org_id"))){//Nutz 可以轻松拿到主表的主键的column
				Org org = re.toEntity(dao.getEntity(Org.class), "org_");////Nutz 可以轻松拿到主表的表名
				orgs.add(org);
				orgs.setUsers(new ArrayList<User>());
				//让临时变量也指向这个org对象
				orgTemp = org;
			}
			//因为上面的sql已经按照主键排序了且上面的代码已经做了主数据的识别,所需这里需要往users里add user即可:
			User user = re.toEntity(dao.getEntity(User.class), "user_");//Nutz 可以轻松拿到从表的表名
			orgTemp.getUsers().add(user);
		}

		return orgs;

	}

@wendal


//代码功能示例 public List<Org> queryByJoin() { //假设Org和User是一对多的关系:一个部门有多个员工 //假设Org类有一个属性: private List<User> users; //现查询Org时同时查询出其员工users //假设Nutz Dao已经通过注解等方式拼接出以下sql: String sqlString = " select org.org_id as org_org_id,org.name as org_name,user.user_id as user_user_id,user.name as user_name from org left join user on org.org_id = user.org_id order by org.org_id, user.user_name "; //查询 Sql sql = Sqls.queryRecord(sqlString);//Nutz Dao 已存在接口 dao.execute(sql);//Nutz Dao 已存在接口 List<Record> records = sql.getList(Record.class);//Nutz Dao 已存在接口 //返回封装好的结果集 List<Org> orgs = new ArrayList<Org>(); //解析主数据和从数据 String orgIdTemp = null;//临时变量,假设数据库所有表主键是字符串类型 String Org orgTemp = null;//临时变量 for (Record re : records) { //关键一步,识别主数据,保证主数据个数。 if(orgIdTemp==null || !orgIdTemp.equals(re.getString("org_org_id"))){//Nutz 可以轻松拿到主表的主键的column Org org = re.toEntity(dao.getEntity(Org.class), "org_");////Nutz 可以轻松拿到主表的表名 orgs.add(org); orgs.setUsers(new ArrayList<User>()); //让临时变量也指向这个org对象 orgTemp = org; } //因为上面的sql已经按照主键排序了且上面的代码已经做了主数据的识别,所需这里需要往users里add user即可: User user = re.toEntity(dao.getEntity(User.class), "user_");//Nutz 可以轻松拿到从表的表名 orgTemp.getUsers().add(user); } return orgs; }

用editplus写的,没语法提示,见谅哈 @wendal

恩,算是明白你的意思了

@wendal Nutz dao可以默认实现吗?这样以后一对多的情况,我就不需要重复写这样的代码啦。

@wendal 我上面的样例代码是不严谨的,因为仅仅考虑到关联1个一对多的属性,如果是多个1对多的属性,使用left join on的话,查询结果集中,重复的将不仅仅是主表数据了,假设有N(N>1)个一对多的属性,那么前面(N-1)个属性的结果集个数都会变大,因为二次left join on下一个属性的时候,结果集被重复了。

所以不靠谱呀,需要的话自己封装一下就好了

@wendal 其实通过排序可以解决这个问题(按照主表主键、从表1主键、从表二主键........),但是如果结果集很大,排序造成的开销也很大,也不划算,所以,我还是具体情况具体对待啦,自己做个简单的封装。

@wendal 我绕了一圈,还是觉得Nutz Dao针对一对多和多对多的情况使用子查询的方式是稳妥的,合理的。特别是新增Map<String, Condition> cnds参数后,这个cnds里可能包含了针对每个关联属性的排序条件。我之前的想法太简单了。。。。。。。。。呜呜呜

@wendal Nutz Dao最新的快照针对queryByJoin的情况已经添加了对cnds支持的接口:
public List queryByJoin(Class classOfT, String regex, Condition cnd, Pager pager, Map<String, Condition> cnds)
可不可以针对fetchByJoin的情况也新增一个支持cnds的接口:
public T fetchByJoin(Class classOfT, String regex, Condition cnd, final Map<String, Condition> cnds)

貌似我加了呀fetchByJoin

忘记push了? 我看看

添加回复
请先登陆
回到顶部