<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          Mybatis-Plus 官方神器發(fā)布!

          共 15110字,需瀏覽 31分鐘

           ·

          2021-11-15 02:49


          今天介紹一個(gè) MyBatis - Plus 官方發(fā)布的神器:mybatis-mate 為 mp 企業(yè)級(jí)模塊,支持分庫(kù)分表,數(shù)據(jù)審計(jì)、數(shù)據(jù)敏感詞過(guò)濾(AC算法),字段加密,字典回寫(數(shù)據(jù)綁定),數(shù)據(jù)權(quán)限,表結(jié)構(gòu)自動(dòng)生成 SQL 維護(hù)等,旨在更敏捷優(yōu)雅處理數(shù)據(jù)。

          主要功能

          • 字典綁定

          • 字段加密

          • 數(shù)據(jù)脫敏

          • 表結(jié)構(gòu)動(dòng)態(tài)維護(hù)

          • 數(shù)據(jù)審計(jì)記錄

          • 數(shù)據(jù)范圍(數(shù)據(jù)權(quán)限)

          • 數(shù)據(jù)庫(kù)分庫(kù)分表、動(dòng)態(tài)據(jù)源、讀寫分離、數(shù)- - 據(jù)庫(kù)健康檢查自動(dòng)切換。


          使用


          1 依賴導(dǎo)入


          Spring Boot 引入自動(dòng)依賴注解包
          <dependency>  <groupId>com.baomidougroupId>  <artifactId>mybatis-mate-starterartifactId>  <version>1.0.8version>dependency>


          注解(實(shí)體分包使用)


          <dependency>  <groupId>com.baomidougroupId>  <artifactId>mybatis-mate-annotationartifactId>  <version>1.0.8version>dependency>


          2 字段數(shù)據(jù)綁定(字典回寫)


          例如 user_sex 類型 sex 字典結(jié)果映射到 sexText 屬性


          @FieldDict(type = "user_sex", target = "sexText")private Integer sex;
          private String sexText;

          實(shí)現(xiàn) IDataDict 接口提供字典數(shù)據(jù)源,注入到 Spring 容器即可。


          @Componentpublic class DataDict implements IDataDict {
          /** * 從數(shù)據(jù)庫(kù)或緩存中獲取 */ private Map<String, String> SEX_MAP = new ConcurrentHashMap<String, String>() {{ put("0", "女"); put("1", "男"); }};
          @Override public String getNameByCode(FieldDict fieldDict, String code) { System.err.println("字段類型:" + fieldDict.type() + ",編碼:" + code); return SEX_MAP.get(code); }}


          3 字段加密



          屬性 @FieldEncrypt 注解即可加密存儲(chǔ),會(huì)自動(dòng)解密查詢結(jié)果,支持全局配置加密密鑰算法,及注解密鑰算法,可以實(shí)現(xiàn) IEncryptor 注入自定義算法。
          @FieldEncrypt(algorithm = Algorithm.PBEWithMD5AndDES)private String password;



          4 字段脫敏



          屬性 @FieldSensitive 注解即可自動(dòng)按照預(yù)設(shè)策略對(duì)源數(shù)據(jù)進(jìn)行脫敏處理,默認(rèn) SensitiveType 內(nèi)置 9 種常用脫敏策略。

          例如:中文名、銀行卡賬號(hào)、手機(jī)號(hào)碼等 脫敏策略。也可以自定義策略如下:
          @FieldSensitive(type = "testStrategy")private String username;
          @FieldSensitive(type = SensitiveType.mobile)private String mobile;


          自定義脫敏策略 testStrategy 添加到默認(rèn)策略中注入 Spring 容器即可。


          @Configurationpublic class SensitiveStrategyConfig {
          /** * 注入脫敏策略 */ @Bean public ISensitiveStrategy sensitiveStrategy() { // 自定義 testStrategy 類型脫敏處理 return new SensitiveStrategy().addStrategy("testStrategy", t -> t + "***test***"); }}


          例如文章敏感詞過(guò)濾


          /** * 演示文章敏感詞過(guò)濾 */@RestControllerpublic class ArticleController {    @Autowired    private SensitiveWordsMapper sensitiveWordsMapper;
          // 測(cè)試訪問(wèn)下面地址觀察請(qǐng)求地址、界面返回?cái)?shù)據(jù)及控制臺(tái)( 普通參數(shù) ) // 無(wú)敏感詞 http://localhost:8080/info?content=tom&see=1&age=18 // 英文敏感詞 http://localhost:8080/info?content=my%20content%20is%20tomcat&see=1&age=18 // 漢字敏感詞 http://localhost:8080/info?content=%E7%8E%8B%E5%AE%89%E7%9F%B3%E5%94%90%E5%AE%8B%E5%85%AB%E5%A4%A7%E5%AE%B6&see=1 // 多個(gè)敏感詞 http://localhost:8080/info?content=%E7%8E%8B%E5%AE%89%E7%9F%B3%E6%9C%89%E4%B8%80%E5%8F%AA%E7%8C%ABtomcat%E6%B1%A4%E5%A7%86%E5%87%AF%E7%89%B9&see=1&size=6 // 插入一個(gè)字變成非敏感詞 http://localhost:8080/info?content=%E7%8E%8B%E7%8C%AB%E5%AE%89%E7%9F%B3%E6%9C%89%E4%B8%80%E5%8F%AA%E7%8C%ABtomcat%E6%B1%A4%E5%A7%86%E5%87%AF%E7%89%B9&see=1&size=6 @GetMapping("/info") public String info(Article article) throws Exception { return ParamsConfig.toJson(article); }

          // 添加一個(gè)敏感詞然后再去觀察是否生效 http://localhost:8080/add // 觀察【貓】這個(gè)詞被過(guò)濾了 http://localhost:8080/info?content=%E7%8E%8B%E5%AE%89%E7%9F%B3%E6%9C%89%E4%B8%80%E5%8F%AA%E7%8C%ABtomcat%E6%B1%A4%E5%A7%86%E5%87%AF%E7%89%B9&see=1&size=6 // 嵌套敏感詞處理 http://localhost:8080/info?content=%E7%8E%8B%E7%8C%AB%E5%AE%89%E7%9F%B3%E6%9C%89%E4%B8%80%E5%8F%AA%E7%8C%ABtomcat%E6%B1%A4%E5%A7%86%E5%87%AF%E7%89%B9&see=1&size=6 // 多層嵌套敏感詞 http://localhost:8080/info?content=%E7%8E%8B%E7%8E%8B%E7%8C%AB%E5%AE%89%E7%9F%B3%E5%AE%89%E7%9F%B3%E6%9C%89%E4%B8%80%E5%8F%AA%E7%8C%ABtomcat%E6%B1%A4%E5%A7%86%E5%87%AF%E7%89%B9&see=1&size=6 @GetMapping("/add") public String add() throws Exception { Long id = 3L; if (null == sensitiveWordsMapper.selectById(id)) { System.err.println("插入一個(gè)敏感詞:" + sensitiveWordsMapper.insert(new SensitiveWords(id, "貓"))); // 插入一個(gè)敏感詞,刷新算法引擎敏感詞 SensitiveWordsProcessor.reloadSensitiveWords(); } return "ok"; }
          // 測(cè)試訪問(wèn)下面地址觀察控制臺(tái)( 請(qǐng)求json參數(shù) ) // idea 執(zhí)行 resources 目錄 TestJson.http 文件測(cè)試 @PostMapping("/json") public String json(@RequestBody Article article) throws Exception { return ParamsConfig.toJson(article); }}


          5 DDL 數(shù)據(jù)結(jié)構(gòu)自動(dòng)維護(hù)


          解決升級(jí)表結(jié)構(gòu)初始化,版本發(fā)布更新 SQL 維護(hù)問(wèn)題,目前支持 MySql、


          PostgreSQL。@Componentpublic class PostgresDdl implements IDdl {
          /** * 執(zhí)行 SQL 腳本方式 */ @Override public List getSqlFiles() { return Arrays.asList( // 內(nèi)置包方式 "db/tag-schema.sql", // 文件絕對(duì)路徑方式 "D:\\db\\tag-data.sql" ); }}



          不僅僅可以固定執(zhí)行,也可以動(dòng)態(tài)執(zhí)行!!


          ddlScript.run(new StringReader("DELETE FROM user;\n" +                "INSERT INTO user (id, username, password, sex, email) VALUES\n" +                "(20, 'Duo', '123456', 0, '[email protected]');"));


          它還支持多數(shù)據(jù)源執(zhí)行!!!


          @Componentpublic class MysqlDdl implements IDdl {
          @Override public void sharding(Consumer consumer) { // 多數(shù)據(jù)源指定,主庫(kù)初始化從庫(kù)自動(dòng)同步 String group = "mysql"; ShardingGroupProperty sgp = ShardingKey.getDbGroupProperty(group); if (null != sgp) { // 主庫(kù) sgp.getMasterKeys().forEach(key -> { ShardingKey.change(group + key); consumer.accept(this); }); // 從庫(kù) sgp.getSlaveKeys().forEach(key -> { ShardingKey.change(group + key); consumer.accept(this); }); } }
          /** * 執(zhí)行 SQL 腳本方式 */ @Override public List getSqlFiles() { return Arrays.asList("db/user-mysql.sql"); }}


          6 動(dòng)態(tài)多數(shù)據(jù)源主從自由切換


          @Sharding 注解使數(shù)據(jù)源不限制隨意使用切換,你可以在 mapper 層添加注解,按需求指哪打哪!!


          @Mapper@Sharding("mysql")public interface UserMapper extends BaseMapper<User> {
          @Sharding("postgres") Long selectByUsername(String username);}


          你也可以自定義策略統(tǒng)一調(diào)兵遣將


          @Componentpublic class MyShardingStrategy extends RandomShardingStrategy {
          /** * 決定切換數(shù)據(jù)源 key {@link ShardingDatasource} * * @param group 動(dòng)態(tài)數(shù)據(jù)庫(kù)組 * @param invocation {@link Invocation} * @param sqlCommandType {@link SqlCommandType} */ @Override public void determineDatasourceKey(String group, Invocation invocation, SqlCommandType sqlCommandType) { // 數(shù)據(jù)源組 group 自定義選擇即可, keys 為數(shù)據(jù)源組內(nèi)主從多節(jié)點(diǎn),可隨機(jī)選擇或者自己控制 this.changeDatabaseKey(group, sqlCommandType, keys -> chooseKey(keys, invocation)); }}


          可以開(kāi)啟主從策略,當(dāng)然也是可以開(kāi)啟健康檢查!具體配置:


          mybatis-mate:  sharding:    health: true # 健康檢測(cè)    primary: mysql # 默認(rèn)選擇數(shù)據(jù)源    datasource:      mysql: # 數(shù)據(jù)庫(kù)組        - key: node1          ...        - key: node2          cluster: slave # 從庫(kù)讀寫分離時(shí)候負(fù)責(zé) sql 查詢操作,主庫(kù) master 默認(rèn)可以不寫          ...      postgres:        - key: node1 # 數(shù)據(jù)節(jié)點(diǎn)          ...


          7 分布式事務(wù)日志打印


          部分配置如下:


          /** * 

          * 性能分析攔截器,用于輸出每條 SQL 語(yǔ)句及其執(zhí)行時(shí)間 *

          */@Slf4j@Component@Intercepts({@Signature(type = StatementHandler.class, method = "query", args = {Statement.class, ResultHandler.class}), @Signature(type = StatementHandler.class, method = "update", args = {Statement.class}), @Signature(type = StatementHandler.class, method = "batch", args = {Statement.class})})public class PerformanceInterceptor implements Interceptor { /** * SQL 執(zhí)行最大時(shí)長(zhǎng),超過(guò)自動(dòng)停止運(yùn)行,有助于發(fā)現(xiàn)問(wèn)題。 */ private long maxTime = 0; /** * SQL 是否格式化 */ private boolean format = false; /** * 是否寫入日志文件
          * true 寫入日志文件,不阻斷程序執(zhí)行!
          * 超過(guò)設(shè)定的最大執(zhí)行時(shí)長(zhǎng)異常提示! */ private boolean writeInLog = false;
          @Override public Object intercept(Invocation invocation) throws Throwable { Statement statement; Object firstArg = invocation.getArgs()[0]; if (Proxy.isProxyClass(firstArg.getClass())) { statement = (Statement) SystemMetaObject.forObject(firstArg).getValue("h.statement"); } else { statement = (Statement) firstArg; } MetaObject stmtMetaObj = SystemMetaObject.forObject(statement); try { statement = (Statement) stmtMetaObj.getValue("stmt.statement"); } catch (Exception e) { // do nothing } if (stmtMetaObj.hasGetter("delegate")) {//Hikari try { statement = (Statement) stmtMetaObj.getValue("delegate"); } catch (Exception e) {
          } }
          String originalSql = null; if (originalSql == null) { originalSql = statement.toString(); } originalSql = originalSql.replaceAll("[\\s]+", " "); int index = indexOfSqlStart(originalSql); if (index > 0) { originalSql = originalSql.substring(index); }
          // 計(jì)算執(zhí)行 SQL 耗時(shí) long start = SystemClock.now(); Object result = invocation.proceed(); long timing = SystemClock.now() - start;
          // 格式化 SQL 打印執(zhí)行結(jié)果 Object target = PluginUtils.realTarget(invocation.getTarget()); MetaObject metaObject = SystemMetaObject.forObject(target); MappedStatement ms = (MappedStatement) metaObject.getValue("delegate.mappedStatement"); StringBuilder formatSql = new StringBuilder(); formatSql.append(" Time:").append(timing); formatSql.append(" ms - ID:").append(ms.getId()); formatSql.append("\n Execute SQL:").append(sqlFormat(originalSql, format)).append("\n"); if (this.isWriteInLog()) { if (this.getMaxTime() >= 1 && timing > this.getMaxTime()) { log.error(formatSql.toString()); } else { log.debug(formatSql.toString()); } } else { System.err.println(formatSql); if (this.getMaxTime() >= 1 && timing > this.getMaxTime()) { throw new RuntimeException(" The SQL execution time is too large, please optimize ! "); } } return result; }
          @Override public Object plugin(Object target) { if (target instanceof StatementHandler) { return Plugin.wrap(target, this); } return target; }
          @Override public void setProperties(Properties prop) { String maxTime = prop.getProperty("maxTime"); String format = prop.getProperty("format"); if (StringUtils.isNotEmpty(maxTime)) { this.maxTime = Long.parseLong(maxTime); } if (StringUtils.isNotEmpty(format)) { this.format = Boolean.valueOf(format); } }
          public long getMaxTime() { return maxTime; }
          public PerformanceInterceptor setMaxTime(long maxTime) { this.maxTime = maxTime; return this; }
          public boolean isFormat() { return format; }
          public PerformanceInterceptor setFormat(boolean format) { this.format = format; return this; }
          public boolean isWriteInLog() { return writeInLog; }
          public PerformanceInterceptor setWriteInLog(boolean writeInLog) { this.writeInLog = writeInLog; return this; }
          public Method getMethodRegular(Class clazz, String methodName) { if (Object.class.equals(clazz)) { return null; } for (Method method : clazz.getDeclaredMethods()) { if (method.getName().equals(methodName)) { return method; } } return getMethodRegular(clazz.getSuperclass(), methodName); }
          /** * 獲取sql語(yǔ)句開(kāi)頭部分 * * @param sql * @return */ private int indexOfSqlStart(String sql) { String upperCaseSql = sql.toUpperCase(); Set set = new HashSet<>(); set.add(upperCaseSql.indexOf("SELECT ")); set.add(upperCaseSql.indexOf("UPDATE ")); set.add(upperCaseSql.indexOf("INSERT ")); set.add(upperCaseSql.indexOf("DELETE ")); set.remove(-1); if (CollectionUtils.isEmpty(set)) { return -1; } List list = new ArrayList<>(set); Collections.sort(list, Integer::compareTo); return list.get(0); }
          private final static SqlFormatter sqlFormatter = new SqlFormatter();
          /** * 格式sql * * @param boundSql * @param format * @return */ public static String sqlFormat(String boundSql, boolean format) { if (format) { try { return sqlFormatter.format(boundSql); } catch (Exception ignored) { } } return boundSql; }}


          使用:


          @RestController@AllArgsConstructorpublic class TestController {    private BuyService buyService;
          // 數(shù)據(jù)庫(kù) test 表 t_order 在事務(wù)一致情況無(wú)法插入數(shù)據(jù),能夠插入說(shuō)明多數(shù)據(jù)源事務(wù)無(wú)效 // 測(cè)試訪問(wèn) http://localhost:8080/test // 制造事務(wù)回滾 http://localhost:8080/test?error=true 也可通過(guò)修改表結(jié)構(gòu)制造錯(cuò)誤 // 注釋 ShardingConfig 注入 dataSourceProvider 可測(cè)試事務(wù)無(wú)效情況 @GetMapping("/test") public String test(Boolean error) { return buyService.buy(null != error && error); }}


          8 數(shù)據(jù)權(quán)限


          mapper 層添加注解:


          // 測(cè)試 test 類型數(shù)據(jù)權(quán)限范圍,混合分頁(yè)模式@DataScope(type = "test", value = {        // 關(guān)聯(lián)表 user 別名 u 指定部門字段權(quán)限        @DataColumn(alias = "u", name = "department_id"),        // 關(guān)聯(lián)表 user 別名 u 指定手機(jī)號(hào)字段(自己判斷處理)        @DataColumn(alias = "u", name = "mobile")})@Select("select u.* from user u")List selectTestList(IPage page, Long id, @Param("name") String username);

          模擬業(yè)務(wù)處理邏輯:
          @Beanpublic IDataScopeProvider dataScopeProvider() {    return new AbstractDataScopeProvider() {        @Override        protected void setWhere(PlainSelect plainSelect, Object[] args, DataScopeProperty dataScopeProperty) {            // args 中包含 mapper 方法的請(qǐng)求參數(shù),需要使用可以自行獲取            /*                // 測(cè)試數(shù)據(jù)權(quán)限,最終執(zhí)行 SQL 語(yǔ)句                SELECT u.* FROM user u WHERE (u.department_id IN ('1', '2', '3', '5'))                AND u.mobile LIKE '%1533%'             */            if ("test".equals(dataScopeProperty.getType())) {                // 業(yè)務(wù) test 類型                List dataColumns = dataScopeProperty.getColumns();                for (DataColumnProperty dataColumn : dataColumns) {                    if ("department_id".equals(dataColumn.getName())) {                        // 追加部門字段 IN 條件,也可以是 SQL 語(yǔ)句                        Set<String> deptIds = new HashSet<>();                        deptIds.add("1");                        deptIds.add("2");                        deptIds.add("3");                        deptIds.add("5");                        ItemsList itemsList = new ExpressionList(deptIds.stream().map(StringValue::new).collect(Collectors.toList()));                        InExpression inExpression = new InExpression(new Column(dataColumn.getAliasDotName()), itemsList);                        if (null == plainSelect.getWhere()) {                            // 不存在 where 條件                            plainSelect.setWhere(new Parenthesis(inExpression));                        } else {                            // 存在 where 條件 and 處理                            plainSelect.setWhere(new AndExpression(plainSelect.getWhere(), inExpression));                        }                    } else if ("mobile".equals(dataColumn.getName())) {                        // 支持一個(gè)自定義條件                        LikeExpression likeExpression = new LikeExpression();                        likeExpression.setLeftExpression(new Column(dataColumn.getAliasDotName()));                        likeExpression.setRightExpression(new StringValue("%1533%"));                        plainSelect.setWhere(new AndExpression(plainSelect.getWhere(), likeExpression));                    }                }            }        }    };}


          最終執(zhí)行 SQL 輸出:


          SELECT u.* FROM user u  WHERE (u.department_id IN ('1', '2', '3', '5'))  AND u.mobile LIKE '%1533%' LIMIT 1, 10


          目前僅有付費(fèi)版本,了解更多 mybatis-mate 使用示例詳見(jiàn):

          https://gitee.com/baomidou/mybatis-mate-examples

          來(lái)源:網(wǎng)事全知道


          (完)


          BAT等大廠Java面試經(jīng)驗(yàn)總結(jié)



          想獲取 Java大廠面試題學(xué)習(xí)資料

          掃下方二維碼回復(fù)Java就好了

          瀏覽 59
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          評(píng)論
          圖片
          表情
          推薦
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  国产亚洲欧美精品久久 | 成人在线视频黄色 | 黄色激情视频在线观看 | www.亚洲精品 | 国产每日 |