您好,登錄后才能下訂單哦!
本篇文章為大家展示了怎么在mybatisplus 中使用SQL攔截器實現關聯查詢功能,內容簡明扼要并且容易理解,絕對能使你眼前一亮,通過這篇文章的詳細介紹希望你能有所收獲。
環境信息
jdk: 1.8
springboot: 2.3.4.RELEASE
mybatisplus: 3.4.2
lombok:1.18.12
代碼涉及四個關鍵的類:
JoinBuilder
這是一個建造者類,主要適用于生成關聯查詢的語句
CaseBuilder
這也是一個建造者類,主要是用來生成連接查詢的條件語句
MyQueryWrapper
這是查詢器,這里繼承了官方的QueryWrapper,然后擴展了一些功能。添加了關聯查詢的功能
JoinQueryInterceptor
這是SQL攔截器,在上面使用自定義的查詢器添加了關聯查詢之后就可以使用SQL攔截器進行sql的構造
類關系圖如下:
實現連接條件構造器
package com.jenkin.common.config; import cn.hutool.core.util.ArrayUtil; import com.baomidou.mybatisplus.core.toolkit.StringUtils; import lombok.extern.slf4j.Slf4j; import net.sf.jsqlparser.JSQLParserException; import net.sf.jsqlparser.parser.CCJSqlParserManager; import net.sf.jsqlparser.statement.select.Join; import net.sf.jsqlparser.statement.select.PlainSelect; import net.sf.jsqlparser.statement.select.Select; import java.io.StringReader; import java.util.HashSet; import java.util.Set; /** * @author jenkin * @Since 2021年4月12日14:45:58 * @Version 1.0 * @Description : 關聯查詢join構造器 */ @Slf4j public class JoinBuilder { private StringBuilder sb = new StringBuilder(); /** * 關聯表里面的查詢字段,比如要查詢關聯的用戶表里面的用戶名稱 */ private String[] selectFields; /** * 關聯表 */ private String joinTable; /** * 查詢字段去重 */ Set<String> set = new HashSet<>(); /** * 主表 */ private String mainTable; /** * 關聯類型 */ private String joinType; private static final String LEFT_BRACKET = " ( "; private static final String RIGHT_BRACKET = " ) "; private static final String AND = " AND "; private static final String OR = " OR "; /** * 左連接 */ public static final String LEFT = " left "; /** * 右連接 */ public static final String RIGHT = " right "; /** * 內連接 */ public static final String INNER = " inner "; public JoinBuilder selectField(String... fields) { this.selectFields = fields; if (!ArrayUtil.isEmpty(this.selectFields)) { for (int i = 0; i < this.selectFields.length; i++) { this.selectFields[i] = StringUtils.camelToUnderline(this.selectFields[i]); set.add(this.selectFields[i].toUpperCase()); } } return this; } public Set<String> getSelectFields() { return set; } public String getMainTable() { return mainTable; } public String getSubTable() { return this.joinTable; } /** * @param joinType 關聯類型 JoinBuilder.LEFT,JoinBuilder.RIGHT,JoinBuilder.INNER * @param mainTable 主表 * @param joinTable 關聯表 * @return */ public JoinBuilder join(String joinType, String mainTable, String joinTable) { mainTable = StringUtils.camelToUnderline(mainTable); ; joinTable = StringUtils.camelToUnderline(joinTable); ; this.joinTable = joinTable; this.mainTable = mainTable; this.joinType = joinType; return this; } public static JoinBuilder build() { return new JoinBuilder(); } public JoinBuilder and() { sb.append(AND); return this; } public JoinBuilder or() { sb.append(OR); return this; } public StringBuilder getSql() { return sb; } public JoinBuilder on(CaseBuilder builder) { sb.append(LEFT_BRACKET).append(builder.getSql()).append(RIGHT_BRACKET); return this; } public Join getJoin() { CCJSqlParserManager pm = new CCJSqlParserManager(); String sql = "select * from " + mainTable + " " + joinType + " join " + joinTable + " on " + sb; try { net.sf.jsqlparser.statement.Statement parse = pm.parse(new StringReader(sql)); if (parse instanceof Select) { return ((PlainSelect) ((Select) parse).getSelectBody()).getJoins().get(0); } return null; } catch (JSQLParserException e) { log.warn(sql); e.printStackTrace(); } return null; } /** * @author jenkin * @Since 2021年4月12日14:45:58 * @Version 1.0 * @Description : 條件構造器,局限于關聯查詢 */ public static class CaseBuilder { /** * SQL語句 */ private StringBuilder sb = new StringBuilder(); private static final String LEFT_BRACKET = " ( "; private static final String RIGHT_BRACKET = " ) "; private static final String EQ = "="; private static final String NE = "<>"; private static final String GT = ">"; private static final String LT = "<"; private static final String GT_EQ = ">="; private static final String LT_EQ = "<="; private static final String AND = " AND "; private static final String OR = " OR "; public static CaseBuilder build() { return new CaseBuilder(); } public StringBuilder getSql() { return sb; } /** * 把條件表達式用括號包裹起來 * * @param builder * @return */ public CaseBuilder brackets(CaseBuilder builder) { sb.append(LEFT_BRACKET).append(builder.sb).append(RIGHT_BRACKET); return this; } public CaseBuilder and() { sb.append(AND); return this; } public CaseBuilder or() { sb.append(OR); return this; } /** * 規定左側為主表的列 * ,右側為從表的列,不可以寫反 * 注意,在使用定值查詢的時候 例如 on a.name = b.name and age = 1 * 這個時候一樣要遵循左邊為主表,右邊為關聯表的規則. * 例如 * <p> * 1、 and里面的條件 age字段是存在在主表里面的 那么就寫成 eq("age",1) * 2、如果age字段是在關聯表里面的,那么應該寫成 eq(1,"age") * <p> * 其他的條件語句例如,ne,gt,lt等等也適用這個邏輯 * * @param left 左側列名稱 * @param right 右側列名稱 * @return */ public CaseBuilder eq(Object left, Object right) { if (left instanceof String) { left = StringUtils.camelToUnderline((String) left); } if (right instanceof String) { right = StringUtils.camelToUnderline(String.valueOf(right)); } sb.append(left).append(EQ).append(right); return this; } /** * 規定左側為主表的列 * ,右側為從表的列,不可以寫反 * * @param left 左側列名稱 * @param right 右側列名稱 * @return */ public CaseBuilder ne(String left, Object right) { left = StringUtils.camelToUnderline(left); if (right instanceof String) { right = StringUtils.camelToUnderline(String.valueOf(right)); } sb.append(left).append(NE).append(right); return this; } /** * 關聯查詢一般是列關聯,如果條件里面有值等式,要做特殊處理,目前還不支持 * * @param left * @param right * @return */ @Deprecated public CaseBuilder gt(String left, Object right) { sb.append(left).append(GT).append(right); return this; } /** * 關聯查詢一般是列關聯,如果條件里面有值等式,要做特殊處理,目前還不支持 * * @param left * @param right * @return */ @Deprecated public CaseBuilder gtEq(String left, Object right) { sb.append(left).append(GT_EQ).append(right); return this; } /** * 關聯查詢一般是列關聯,如果條件里面有值等式,要做特殊處理,目前還不支持 * * @param left * @param right * @return */ @Deprecated public CaseBuilder lt(String left, Object right) { sb.append(left).append(LT).append(right); return this; } /** * 關聯查詢一般是列關聯,如果條件里面有值等式,要做特殊處理,目前還不支持 * * @param left * @param right * @return */ @Deprecated public CaseBuilder ltEq(String left, Object right) { sb.append(left).append(LT_EQ).append(right); return this; } } }
定制化QueryWrapper
在這個定制化的查詢器里面添加了一個addJoin的方法用來添加關聯查詢
package com.jenkin.common.config; import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; import com.baomidou.mybatisplus.core.toolkit.CollectionUtils; import com.baomidou.mybatisplus.core.toolkit.StringUtils; import com.jenkin.common.entity.qos.Sort; import java.util.ArrayList; import java.util.List; /** * @author :jenkin * @date :Created at 2020/3/13 12:07 * @description:條件構造器,重寫字符串轉換方法 * @modified By: * @version: 1.0 */ public class MyQueryWrapper<T> extends QueryWrapper<T> { /** * 關聯查詢構造器 */ private final List<JoinBuilder> joinBuilder = new ArrayList<>(); /** * 獲取 columnName * * @param column */ @Override protected String columnToString(String column) { return StringUtils.camelToUnderline(column); } public static<T> MyQueryWrapper<T> query(){ return new MyQueryWrapper<T>(); } /** * 關聯查詢構造 * @param builder * @return */ public MyQueryWrapper<T> addJoin(JoinBuilder builder){ this.joinBuilder.add(builder); return this; } public List<JoinBuilder> getJoinBuilder() { return joinBuilder; } /** * 排序 * @param sorts * @return */ public QueryWrapper<T> sort(List<Sort> sorts){ if(!CollectionUtils.isEmpty(sorts)){ sorts.forEach(item->{ orderBy(item.getSortField()!=null,"asc".equals(item.getSortValue()),item.getSortField()); }); } return this; } }
定義SQL攔截器
通過自定義的SQL攔截器去攔截我們寫好的關聯查詢,然后生成對應的SQL
package com.jenkin.common.config; import com.alibaba.nacos.client.naming.utils.CollectionUtils; import com.baomidou.mybatisplus.core.toolkit.PluginUtils; import com.baomidou.mybatisplus.extension.parser.JsqlParserSupport; import com.baomidou.mybatisplus.extension.plugins.inner.InnerInterceptor; import net.sf.jsqlparser.expression.Alias; import net.sf.jsqlparser.expression.BinaryExpression; import net.sf.jsqlparser.expression.Expression; import net.sf.jsqlparser.expression.Parenthesis; import net.sf.jsqlparser.expression.operators.relational.Between; import net.sf.jsqlparser.expression.operators.relational.IsNullExpression; import net.sf.jsqlparser.schema.Column; import net.sf.jsqlparser.schema.Table; import net.sf.jsqlparser.statement.delete.Delete; import net.sf.jsqlparser.statement.insert.Insert; import net.sf.jsqlparser.statement.select.*; import net.sf.jsqlparser.statement.update.Update; import org.apache.ibatis.binding.MapperMethod; import org.apache.ibatis.executor.Executor; import org.apache.ibatis.mapping.BoundSql; import org.apache.ibatis.mapping.MappedStatement; import org.apache.ibatis.session.ResultHandler; import org.apache.ibatis.session.RowBounds; import java.sql.SQLException; import java.util.ArrayList; import java.util.HashSet; import java.util.List; import java.util.Set; /** * @author jenkin * @className JoinQueryInterceptor * @description TODO * @date 2021/4/12 14:58 */ public class JoinQueryInterceptor extends JsqlParserSupport implements InnerInterceptor { /** * 保存我們的關聯查詢的上下文信息 */ static ThreadLocal<List<JoinBuilder>> joinBuilderThreadLocal = new ThreadLocal<>(); /** * 操作前置處理 * <p> * 改改sql啥的 * * @param executor Executor(可能是代理對象) * @param ms MappedStatement * @param parameter parameter * @param rowBounds rowBounds * @param resultHandler resultHandler * @param boundSql boundSql */ @Override public void beforeQuery(Executor executor, MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException { if (parameter instanceof MapperMethod.ParamMap) { for (Object value : ((MapperMethod.ParamMap) parameter).values()) { if (value instanceof MyQueryWrapper) { List<JoinBuilder> joinBuilders = ((MyQueryWrapper) value).getJoinBuilder(); if(!CollectionUtils.isEmpty(joinBuilders)){ joinBuilderThreadLocal.set(joinBuilders); try { logger.debug("開始添加關聯查詢SQL"); String s = this.parserSingle(boundSql.getSql(), parameter); logger.debug("加了關聯查詢的SQL : "+ s); PluginUtils.MPBoundSql mpBs = PluginUtils.mpBoundSql(boundSql); mpBs.sql(s); }finally { joinBuilderThreadLocal.remove(); } return; } } } } } /** * 查詢 */ @Override protected void processSelect(Select select, int index, String sql, Object obj) { List<JoinBuilder> joinBuilders = joinBuilderThreadLocal.get(); PlainSelect selectBody = (PlainSelect) select.getSelectBody(); if (selectBody.getFromItem().getAlias()==null) { selectBody.getFromItem().setAlias(new Alias("mainjointable")); } setJoins(selectBody,joinBuilders); } private void setJoins(PlainSelect selectBody,List<JoinBuilder> joinBuilders) { List<SelectItem> selectItems = selectBody.getSelectItems(); Expression where = selectBody.getWhere(); List<Join> joins = new ArrayList<>(); for (int i = 0; i < joinBuilders.size(); i++) { JoinBuilder joinBuilder = joinBuilders.get(i); Join builderJoin = joinBuilder.getJoin(); Set<String> selectFields = new HashSet<>(joinBuilder.getSelectFields()); Join join = new Join(); join.setLeft(builderJoin.isLeft()); join.setRight(builderJoin.isRight()); join.setInner(builderJoin.isInner()); Table table = new Table(joinBuilder.getSubTable()); table.setAlias( new Alias("subjointable"+i)); setSelectItems(table,selectFields,selectItems,selectBody); join.setRightItem(table); // Expression expression = getOnExpressionWithTable(joinBuilder); Expression onExpression = joinBuilder.getJoin().getOnExpression(); selectFields = new HashSet<>(joinBuilder.getSelectFields()); setOnCase(onExpression,table,selectFields,(Table) selectBody.getFromItem(),false); join.setOnExpression(onExpression); joins.add(join); selectFields = new HashSet<>(joinBuilder.getSelectFields()); setWhere(where,table,selectFields,(Table) selectBody.getFromItem()); } selectBody.setJoins(joins); } // private Expression getOnExpressionWithTable(JoinBuilder joinBuilder) { setWhere(joinBuilder.getJoin().getOnExpression(),); // return joinBuilder.getJoin().getOnExpression(); // } private void setSelectItems(Table table, Set<String> selectFields, List<SelectItem> selectItems, PlainSelect selectBody) { for (SelectItem selectItem : selectItems) { if (selectItem instanceof SelectExpressionItem) { if (((SelectExpressionItem) selectItem).getExpression() instanceof Column && selectBody.getFromItem() instanceof Table) { Column expression = (Column) ((SelectExpressionItem) selectItem).getExpression(); if (expression.getTable()==null&&selectFields.contains(expression.getColumnName().toUpperCase())){ expression.setTable(table); selectFields.remove(expression.getColumnName().toUpperCase()); }else if(expression.getTable()==null){ expression.setTable((Table) selectBody.getFromItem()); } } } } if (!selectFields.isEmpty()){ for (String selectField : selectFields) { SelectExpressionItem selectExpressionItem = new SelectExpressionItem(); Column column = new Column(); column.setTable(table); column.setColumnName(selectField); selectExpressionItem.setExpression(column); selectItems.add(selectExpressionItem); } } } /** * * @param on * @param subTable * @param joinSelectFields * @param sourceTable * @param isLeft 是否是左側列,如果是那么就是主表,如果false,那么就是關聯表,如果為null,那么就需要根據join字段判斷 */ private void setOnCase(Object on, Table subTable, Set<String> joinSelectFields , Table sourceTable,Boolean isLeft) { if (on==null) { return; } if (on instanceof Column) { Column column = (Column) on; if((column).getTable()==null &&isLeft!=null){ (column).setTable(isLeft?sourceTable:subTable); } if (isLeft==null&&column.getTable()==null){ (column).setTable(joinSelectFields.contains(column.getColumnName().toUpperCase())?subTable:sourceTable); } }else if (on instanceof BinaryExpression){ setOnCase(((BinaryExpression) on).getLeftExpression(),subTable,joinSelectFields,sourceTable,true); setOnCase(((BinaryExpression) on).getRightExpression(),subTable,joinSelectFields,sourceTable,false); }else if (on instanceof Parenthesis){ setOnCase(((Parenthesis) on).getExpression(),subTable,joinSelectFields,sourceTable,false); }else if(on instanceof IsNullExpression){ setOnCase(((IsNullExpression) on).getLeftExpression(),subTable,joinSelectFields,sourceTable,null); }else if (on instanceof Between){ setOnCase(((Between) on).getLeftExpression(),subTable,joinSelectFields,sourceTable,null); } //有其他條件再補充 } private void setWhere(Object where, Table subTable, Set<String> joinSelectFields , Table sourceTable) { if (where==null) { return; } if (where instanceof Column) { Column column = (Column) where; if((column).getTable()==null&&joinSelectFields.contains((column).getColumnName().toUpperCase())){ (column).setTable(subTable); }else if((column).getTable()==null){ (column).setTable(sourceTable); } }else if (where instanceof BinaryExpression){ setWhere(((BinaryExpression) where).getLeftExpression(),subTable,joinSelectFields,sourceTable); setWhere(((BinaryExpression) where).getRightExpression(),subTable,joinSelectFields,sourceTable); }else if (where instanceof Parenthesis){ setWhere(((Parenthesis) where).getExpression(),subTable,joinSelectFields,sourceTable); }else if(where instanceof IsNullExpression){ setWhere(((IsNullExpression) where).getLeftExpression(),subTable,joinSelectFields,sourceTable); }else if(where instanceof Between){ setWhere(((Between) where).getLeftExpression(),subTable,joinSelectFields,sourceTable); } //有其他條件再補充 } }
注入攔截器
緊接著只需要在mybatisplus的配置文件里面注入這個攔截器就可以了
使用示例
使用的過程我們分為兩步:
添加字段到主表PO
如圖,紅框中的部分是我們添加的需要從其他表里面關聯查詢的字段,注意這些字段需要使用@TableField注解標注,并且 select字段和exist字段都要為false,不然會影響新增和修改操作
queryWrapper構造關聯查詢
把字段添加好之后就可以開始寫關聯查詢了,下奶的示例應該是一個涵蓋了大部分場景的示例了,多表關聯,多條件關聯,等等
MyQueryWrapper<MenuPo> queryWrapper = new MyQueryWrapper<>(); //添加一個關聯表查詢,關聯用戶表 queryWrapper.addJoin( JoinBuilder.build() //查詢用戶表里面的用戶名稱和用戶郵箱字段 .selectField(MenuPo.Fields.userName, MenuPo.Fields.userEmail) //使用左連接關聯 .join(JoinBuilder.LEFT, MenuPo.class, UserPo.class) //設置關聯條件 .on(JoinBuilder.CaseBuilder.build() //主表的創建人字段等于關聯表的用戶編碼字段 // 注意,在條件中默認是第一個參數為主表的字段,第二個參數為關聯表的字段 .eq(BasePo.Fields.createdBy, UserPo.Fields.userCode) ) //再添加一個關聯查詢,關聯角色表 ).addJoin( JoinBuilder.build() //查血角色表里面的角色名稱 .selectField(MenuPo.Fields.roleName) //左連接 .join(JoinBuilder.LEFT,MenuPo.class, RolePo.class) //關聯條件 .on(JoinBuilder.CaseBuilder.build() //code等于角色code .eq(MenuPo.Fields.code, RolePo.Fields.roleCode) //并且 .and() //括號 .brackets( //parent =-1 or parent =1 JoinBuilder.CaseBuilder.build() .eq(MenuPo.Fields.parent,-1) .or() .eq(MenuPo.Fields.parent,1) ) ) //外層篩選條件,用戶名=jenkin ).eq(MenuPo.Fields.userName,"jenkin"); //執行查詢 menuService.list(queryWrapper);
可以在控制臺看到執行的SQL:
SELECT mainjointable.id, mainjointable.NAME, mainjointable.CODE, mainjointable.parent, mainjointable.menu_level, mainjointable.permissions, mainjointable.menu_url, mainjointable.menu_icon, mainjointable.menu_order, mainjointable.menu_type, mainjointable.delete_flag, mainjointable.created_by, mainjointable.creation_date, mainjointable.last_update_date, mainjointable.last_updated_by, mainjointable.version_number, subjointable0.USER_EMAIL, subjointable0.USER_NAME, subjointable1.ROLE_NAME FROM lsc_menu AS mainjointable LEFT JOIN lsc_user AS subjointable0 ON ( mainjointable.created_by = subjointable0.user_code ) LEFT JOIN lsc_role AS subjointable1 ON ( mainjointable.CODE = subjointable1.role_code AND ( mainjointable.parent = - 1 OR mainjointable.parent = 1 ) ) WHERE mainjointable.delete_flag = 0 AND ( subjointable0.user_name = ?)
上述內容就是怎么在mybatisplus 中使用SQL攔截器實現關聯查詢功能,你們學到知識或技能了嗎?如果還想學到更多技能或者豐富自己的知識儲備,歡迎關注億速云行業資訊頻道。
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。