? jdbc-plus是一款基於JdbcTemplate增強工具包, 已實現分頁、多租戶、動態表名等外掛,可與mybatis、mybatis-plus等混合使用

deeround發表於2023-05-12

? jdbc-plus簡介

? jdbc-plus是一款基於JdbcTemplate增強工具包,
基於JdbcTemplate已實現分頁、多租戶、動態表名等外掛,可自定義擴充套件外掛,可與mybatis、mybatis-plus等混合使用。專案地址:https://github.com/deeround/jdbc-plus

? 特性

  • 使用簡單,對程式碼入侵很小,可與mybatis、mybatis-plus等混合使用。
  • 可自定義任意擴充套件外掛
  • 免費開源,可任意使用修改程式碼
  • 是對ORM框架的增強不做任何改變,當需要動態執行SQL不是很方面使用ORM框架執行SQL時,jdbc-plus就能發揮作用

? 外掛(持續擴充套件中)

已內建以下外掛,開箱即用,還可以自行擴充套件外掛,擴充套件外掛方法十分簡單。

  • 分頁外掛:與PageHelper使用方法一致,還可以註冊不支援的資料庫
  • 多租戶外掛:與mybatis-plus多租戶外掛使用方法一致,理論上與mybatis-plus多租戶外掛支援度一樣
  • 動態表名外掛:與mybatis-plus動態表名外掛使用方法一致
  • 更多外掛:持續關注jdbc-plus倉庫,倉庫包含所有外掛原始碼以及使用示例

快速開始

  1. 引入jdbc-plus-spring-boot-starter
<dependency>
    <groupId>com.github.deeround</groupId>
    <artifactId>jdbc-plus-spring-boot-starter</artifactId>
    <version>${version}</version>
</dependency>
  1. 注入需要使用的外掛(需要哪個注入哪個,不需要的註釋掉即可)
@Configuration
public class JdbcPlusConfig {

    /**
     * TenantLineInterceptor是內建的多租戶外掛
     */
    @Bean
    @Order(1)
    public IInterceptor tenantLineInterceptor() {
        return new TenantLineInterceptor(new TenantLineHandler() {
            /**
             * 當前租戶ID
             */
            @Override
            public Expression getTenantId() {
                String currentTenantId = "test_tenant_1";//可以從請求上下文中獲取(cookie、session、header等)
                return new StringValue(currentTenantId);
            }

            /**
             * 租戶欄位名
             */
            @Override
            public String getTenantIdColumn() {
                return "tenant_id";
            }

            /**
             * 根據表名判斷是否忽略拼接多租戶條件
             */
            @Override
            public boolean ignoreTable(String tableName) {
                return TenantLineHandler.super.ignoreTable(tableName);
            }
        });
    }

    /**
     * DynamicTableNameInterceptor是內建的動態表名外掛
     */
    @Bean
    @Order(2)
    public IInterceptor dynamicTableNameInterceptor() {
        return new DynamicTableNameInterceptor(new TableNameHandler() {
            @Override
            public String dynamicTableName(String sql, String tableName) {
                if ("test_log".equals(tableName)) {
                    return tableName + "_" + LocalDateTime.now().getYear();
                }
                return tableName;
            }
        });
    }

    /**
     * PaginationInterceptor是內建的分頁外掛(分頁外掛一般情況放置最後)
     */
    @Bean
    @Order(9)
    public IInterceptor paginationInterceptor() {
        return new PaginationInterceptor();
    }

    /**
     * 自定義外掛注入,注入位置按實際情況
     */
    @Bean
    @Order(0)
    public IInterceptor myStatInterceptor() {
        return new MyStatInterceptor();
    }
}
  1. 正常使用JdbcTemplate執行SQL語句,程式碼零入侵,使用體驗超棒
    @Autowired
    JdbcTemplate jdbcTemplate;

    public void insert() {
        this.jdbcTemplate.update("insert into test_user(id,name) values('1','wangwu')");
        //最終執行SQL:insert into test_user(id,name,tenant_id) values('1','wangwu','test_tenant_1')
    }

    public void delete() {
        this.jdbcTemplate.update("delete from test_user where id='1'");
        //最終執行SQL:delete from test_user where id='1' and tenant_id='test_tenant_1'
    }

    public void update() {
        this.jdbcTemplate.update("update test_user set name='lisi' where id='1'");
        //最終執行SQL:update test_user set name='lisi' where id='1' and tenant_id='test_tenant_1'
    }

    public List<Map<String, Object>> query() {
        return this.jdbcTemplate.queryForList("select * from test_user");
        //最終執行SQL:select * from test_user where tenant_id='test_tenant_1'
    }

    public PageInfo<Map<String, Object>> page1() {
        PageHelper.startPage(1, 2);
        List<Map<String, Object>> list = this.jdbcTemplate.queryForList("select * from test_user");
        //最終執行SQL:select * from test_user LIMIT 0,2
        PageInfo<Map<String, Object>> page = new PageInfo<>(list);
        //PageInfo物件包含了分頁資訊(總行數等)
        return page;
    }

多租戶外掛

  1. 注入多租戶外掛
    /**
     * TenantLineInterceptor是內建的多租戶外掛外掛
     */
    @Bean
    @Order(1)
    public IInterceptor tenantLineInterceptor() {
        return new TenantLineInterceptor(new TenantLineHandler() {
            /**
             * 當前租戶ID
             */
            @Override
            public Expression getTenantId() {
                String currentTenantId = "test_tenant_1";//可以從請求上下文中獲取(cookie、session、header等)
                return new StringValue(currentTenantId);
            }

            /**
             * 租戶欄位名
             */
            @Override
            public String getTenantIdColumn() {
                return "tenant_id";
            }

            /**
             * 根據表名判斷是否忽略拼接多租戶條件
             */
            @Override
            public boolean ignoreTable(String tableName) {
                return TenantLineHandler.super.ignoreTable(tableName);
            }
        });
    }
  1. service層執行SQL時自動新增租戶欄位
    public void insert() {
        this.jdbcTemplate.update("insert into test_user(id,name) values('1','wangwu')");
        //最終執行SQL:insert into test_user(id,name,tenant_id) values('1','wangwu','test_tenant_1')
    }

    public void delete() {
        this.jdbcTemplate.update("delete from test_user");
        //最終執行SQL:delete from test_user where tenant_id='test_tenant_1'
    }

    public void update() {
        this.jdbcTemplate.update("update test_user set name='lisi' where id='1'");
        //最終執行SQL:update test_user set name='lisi' where id='1' and tenant_id='test_tenant_1'
    }

    public List<Map<String, Object>> query() {
        return this.jdbcTemplate.queryForList("select * from test_user");
        //最終執行SQL:select * from test_user where tenant_id='test_tenant_1'
    }

分頁外掛

  1. 注入分頁外掛
    /**
     * PaginationInterceptor是內建的分頁外掛(分頁外掛一般情況放置最後)
     */
    @Bean
    @Order(9)
    public IInterceptor paginationInterceptor() {
        return new PaginationInterceptor();
    }
  1. service層執行SQL時自動對SQL進行分頁查詢
    public PageInfo<Map<String, Object>> page1() {
        PageHelper.startPage(1, 2);
        List<Map<String, Object>> list = this.jdbcTemplate.queryForList("select * from test_user");
        //最終執行SQL:select * from test_user LIMIT 0,2
        PageInfo<Map<String, Object>> page = new PageInfo<>(list);
        //PageInfo物件包含了分頁資訊(總行數等)
        return page;
    }

    public PageInfo<Map<String, Object>> page2() {
        PageHelper.startPage(2, 2);
        List<Map<String, Object>> list = this.jdbcTemplate.queryForList("select * from test_user");
        //最終執行SQL:select * from test_user LIMIT 2,2
        PageInfo<Map<String, Object>> page = new PageInfo<>(list);
        //PageInfo物件包含了分頁資訊(總行數等)
        return page;
    }
  1. 自定義分頁

當外掛不支援的資料庫分頁,可以透過PageHelper.registerDialectAlias(String alias, Class clazz) 註冊一個自己分頁實現類即可,也可以覆蓋已支援的資料庫分頁。

動態表名外掛

  1. 注入分頁外掛
    /**
     * DynamicTableNameInterceptor是內建的動態表名外掛
     */
    @Bean
    @Order(2)
    public IInterceptor dynamicTableNameInterceptor() {
        return new DynamicTableNameInterceptor(new TableNameHandler() {
            @Override
            public String dynamicTableName(String sql, String tableName) {
                if ("test_log".equals(tableName)) {
                    return tableName + "_" + LocalDateTime.now().getYear();
                }
                return tableName;
            }
        });
    }
  1. service層執行SQL時自動對SQL進行分頁查詢
    public List<Map<String, Object>> getTestLogList() {
        return this.jdbcTemplate.queryForList("select * from test_log");
        //最終執行SQL:select * from test_log_2023
    }

自定義外掛

示例:寫一個列印SQL語句、執行引數、以及執行SQL耗時的監控外掛。

  1. 編寫MyStatInterceptor外掛
/**
 * SQL監控外掛
 */
@Slf4j
public class MyStatInterceptor implements IInterceptor {
    /**
     * 自定義外掛是否支援
     */
    @Override
    public boolean supportMethod(final MethodInvocationInfo methodInfo) {
        return IInterceptor.super.supportMethod(methodInfo);
    }

    /**
     * SQL執行前方法(主要用於對SQL進行修改)
     */
    @Override
    public void beforePrepare(final MethodInvocationInfo methodInfo, JdbcTemplate jdbcTemplate) {
        log.info("執行SQL開始時間:{}", LocalDateTime.now());
        log.info("原始SQL:{}", Arrays.toString(methodInfo.getBatchSql()));
        log.info("呼叫方法名稱:{}", methodInfo.getName());
        log.info("呼叫方法入參:{}", Arrays.toString(methodInfo.getArgs()));

        methodInfo.putUserAttribute("startTime", LocalDateTime.now());
    }

    /**
     * SQL執行完成後方法(主要用於對返回值修改)
     *
     * @param result 原始返回物件
     * @return 處理後的返回物件
     */
    @Override
    public Object beforeFinish(Object result, final MethodInvocationInfo methodInfo, JdbcTemplate jdbcTemplate) {
        log.info("執行SQL結束時間:{}", LocalDateTime.now());
        LocalDateTime startTime = (LocalDateTime) methodInfo.getUserAttribute("startTime");
        log.info("執行SQL耗時:{}毫秒", Duration.between(startTime, LocalDateTime.now()).toMillis());
        return result;
    }
}
  1. 注入自定義外掛
    /**
     * 自定義外掛注入,注入位置按實際情況
     */
    @Bean
    @Order(0)
    public IInterceptor myStatInterceptor() {
        return new MyStatInterceptor();
    }
  1. 檢視效果(檢視列印日誌)
c.g.d.j.p.s.config.MyStatInterceptor     : 原始SQL:select * from test_user
c.g.d.j.p.s.config.MyStatInterceptor     : 入參:[select * from test_user]
c.g.d.j.p.s.config.MyStatInterceptor     : 執行SQL開始時間:2023-04-23T16:35:58.151
c.g.d.j.p.s.config.MyStatInterceptor     : 執行SQL結束時間:2023-04-23T16:35:58.655
c.g.d.j.p.s.config.MyStatInterceptor     : 執行SQL耗時:503毫秒

★ 鳴謝 ★

歡迎各路好漢一起來參與完善 jdbc-plus,感興趣的可以在github點個 ⭐ ,有任何問題和建議歡迎提交 Issue !

https://github.com/baomidou/mybatis-plus

https://github.com/pagehelper/Mybatis-PageHelper

https://github.com/deeround/jdbc-plus

相關文章