多租戶實現之基於Mybatis,Mycat的共享數據庫,共享數據架構


前言

SaaS模式是什么?

傳統的軟件模式是在開發出軟件產品后,需要去客戶現場進行實施,通常部署在局域網,這樣開發、部署及維護的成本都是比較高的。

現在隨着雲服務技術的蓬勃發展,就出現了SaaS模式。

所謂SaaS模式即是把產品部署在雲服務器上,從前的客戶變成了“租戶”,我們按照功能和租用時間對租戶進行收費。

這樣的好處是,用戶可以按自己的需求來購買功能和時間,同時自己不需要維護服務器,而我們作為SaaS提供商也免去了跑到客戶現場實施的麻煩,運維的風險則主要由IaaS提供商來承擔。


 

SaaS多租戶數據庫方案

多租戶技術或稱多重租賃技術,是一種軟件架構技術,

是實現如何在多用戶環境下共用相同的系統或程序組件,並且可確保各用戶間數據的隔離性。

在當下雲計算時代,多租戶技術在共用的數據中心以單一系統架構與服務提供多數客戶端相同甚至可定制化的服務,並且仍可以保障客戶的數據隔離。

目前各種各樣的雲計算服務就是這類技術范疇,例如阿里雲數據庫服務(RDS)、阿里雲服務器等等。

多租戶在數據存儲上存在三種主要的方案,分別是:

獨立數據庫

這是第一種方案,即一個租戶一個數據庫,這種方案的用戶數據隔離級別最高,安全性最好,但成本較高。 優點: 為不同的租戶提供獨立的數據庫,有助於簡化數據模型的擴展設計,滿足不同租戶的獨特需求;如果出現故障,恢復數據比較簡單。 缺點: 增多了數據庫的安裝數量,隨之帶來維護成本和購置成本的增加。 這種方案與傳統的一個客戶、一套數據、一套部署類似,差別只在於軟件統一部署在運營商那里。如果面對的是銀行、醫院等需要非常高數據隔離級別的租戶,可以選擇這種模式,提高租用的定價。如果定價較低,產品走低價路線,這種方案一般對運營商來說是無法承受的。

共享數據庫,隔離數據架構

這是第二種方案,即多個或所有租戶共享Database,但是每個租戶一個Schema(也可叫做一個user)。 優點: 為安全性要求較高的租戶提供了一定程度的邏輯數據隔離,並不是完全隔離;每個數據庫可支持更多的租戶數量。 缺點: 如果出現故障,數據恢復比較困難,因為恢復數據庫將牽涉到其他租戶的數據; 如果需要跨租戶統計數據,存在一定困難。

共享數據庫,共享數據架構

這是第三種方案,即租戶共享同一個Database、同一個Schema,但在表中增加TenantID多租戶的數據字段。這是共享程度最高、隔離級別最低的模式。 優點: 三種方案比較,第三種方案的維護和購置成本最低,允許每個數據庫支持的租戶數量最多。 缺點: 隔離級別最低,安全性最低,需要在設計開發時加大對安全的開發量; 數據備份和恢復最困難,需要逐表逐條備份和還原。 如果希望以最少的服務器為最多的租戶提供服務,並且租戶接受犧牲隔離級別換取降低成本,這種方案最適合。

選擇合理的實現模式 衡量三種模式主要考慮的因素是隔離還是共享。

成本角度因素 隔離性越好,設計和實現的難度和成本越高,初始成本越高。共享性越好,同一運營成本 下支持的用戶越多,運營成本越低。

安全因素 要考慮業務和客戶的安全方面的要求。安全性要求越高,越要傾向於隔離。

從租戶數量上考慮 主要考慮下面一些因素 系統要支持多少租戶?上百?上千還是上萬?可能的租戶越多,越傾向於共享。 平均每個租戶要存儲數據需要的空間大小。存貯的數據越多,越傾向於隔離。 每個租戶的同時訪問系統的最終用戶數量。需要支持的越多,越傾向於隔離。 是否想針對每一租戶提供附加的服務,例如數據的備份和恢復等。這方面的需求越多, 越傾向於隔離

 

多租戶方案之共享數據庫,隔離數據架構

技術選型

  • Mycat中間件(社區活躍,完全開源的分布式數據庫架構)

  • MyBatis

簡要描述

多租戶方案采用的是MyBatis+MyCat。DEMO是基於Spring MVC的web項目。

在用戶操作過程中獲取用戶的id信息,利用MyCat強大的注解功能,根據用戶id將SQL語句路由到對應該用戶的schema或者database去執行。

對SQL加注解的實現則交由MyBatis的插件功能完成,通過自定義MyBatis的Interceptor類,攔截要執行的sql語句加上對應注解。這樣就實現了數據庫的多租戶改造。下面分幾個部分來說明。

MyCat 與MySQL設置

MyCat是一個開源的分布式數據庫系統,是一個實現了MySQL協議的服務器,

前端用戶可以把它看作是一個數據庫代理,用MySQL客戶端工具和命令行訪問,而其后端可以用MySQL原生協議與多個MySQL服務器通信,

也可以用JDBC協議與大多數主流數據庫服務器通信,其核心功能是分表分庫,即將一個大表水平分割為N個小表,存儲在后端MySQL服務器里或者其他數據庫里。

MyCat相當於一個邏輯上的大數據庫,又N多個物理數據庫組成,可以通過各種分庫分表規則(rule)將數據存到規則對應的數據庫或schema或表中。

MyCat對自身不支持的Sql語句提供了一種解決方案——在要執行的SQL語句前添加額外的一段由注解SQL組織的代碼,這樣Sql就能正確執行,這段代碼稱之為“注解”。

注解的使用相當於對mycat不支持的sql語句做了一層透明代理轉發,直接交給目標的數據節點進行sql語句執行,其中注解SQL用於確定最終執行SQL的數據節點。

注解使用方式如下:

<code class="language-html">/*!mycat: schema=node1*/真正執行Sql</code>  

由於這個項目是根據MyCat的SQL注解來選擇在哪個schema或者database上執行的,所以不需要設置rule.xml。

數據庫設置

 1 create database db01;  
 2  CREATE TABLE `bom`  (
 3   `cate_id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '物料編碼',
 4   `parent_id` bigint(20) NULL DEFAULT NULL COMMENT '父物料ID,一級物料為0',
 5   `cate_code` varchar(50) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '物料編碼',
 6   `name` varchar(50) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '物料名稱',
 7   `unit` varchar(20) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '計量單位',
 8   `used_count` double(32, 4) NULL DEFAULT NULL,
 9   `specify` varchar(50) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '規格',
10   `property` tinyint(4) NULL DEFAULT NULL COMMENT '2=自制件,1=采購件',
11   `status` tinyint(4) NULL DEFAULT NULL COMMENT '狀態(0:開啟 1:禁用)',
12   `description` varchar(100) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '描述',
13   PRIMARY KEY (`cate_id`) USING BTREE
14 ) ENGINE = InnoDB AUTO_INCREMENT = 101 CHARACTER SET = utf8 COLLATE = utf8_general_ci COMMENT = '物料表' ROW_FORMAT = Dynamic;
15 
16 create database db02;  
17 CREATE TABLE `bom`  (
18   `cate_id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '物料編碼',
19   `parent_id` bigint(20) NULL DEFAULT NULL COMMENT '父物料ID,一級物料為0',
20   `cate_code` varchar(50) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '物料編碼',
21   `name` varchar(50) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '物料名稱',
22   `unit` varchar(20) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '計量單位',
23   `used_count` double(32, 4) NULL DEFAULT NULL,
24   `specify` varchar(50) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '規格',
25   `property` tinyint(4) NULL DEFAULT NULL COMMENT '2=自制件,1=采購件',
26   `status` tinyint(4) NULL DEFAULT NULL COMMENT '狀態(0:開啟 1:禁用)',
27   `description` varchar(100) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '描述',
28   PRIMARY KEY (`cate_id`) USING BTREE
29 ) ENGINE = InnoDB AUTO_INCREMENT = 101 CHARACTER SET = utf8 COLLATE = utf8_general_ci COMMENT = '物料表' ROW_FORMAT = Dynamic;

設置兩個數據庫分表為db01,db02,兩個庫中都有bom。

MyCat的配置

server.xml

 1 <?xml version="1.0" encoding="UTF-8"?>
 2 <!DOCTYPE mycat:server SYSTEM "server.dtd">
 3 <mycat:server xmlns:mycat="http://io.mycat/">
 4     <system>
 5     <property name="useSqlStat">0</property> 
 6     <property name="useGlobleTableCheck">0</property>  
 7     <property name="sequnceHandlerType">2</property>
 8     <property name="handleDistributedTransactions">0</property>
 9     <property name="useOffHeapForMerge">1</property>
10         <property name="memoryPageSize">1m</property>
11         <property name="spillsFileBufferSize">1k</property>
12         <property name="useStreamOutput">0</property>
13         <property name="systemReserveMemorySize">384m</property>
14         <property name="useZKSwitch">true</property>
15     </system>
16     
17     <user name="root">
18         <property name="password">james</property>
19         <property name="schemas">james</property>
20     </user>
21 
22 </mycat:server>

server.xml主要是設置登錄用戶名密碼,登錄端口之類的信息。

重頭戲是schema.xml的設置

 1 <?xml version="1.0"?>
 2 <!DOCTYPE mycat:schema SYSTEM "schema.dtd">
 3 <mycat:schema xmlns:mycat="http://io.mycat/">
 4 
 5     <schema name="james" checkSQLschema="false" sqlMaxLimit="100">
 6         <table name="bom" dataNode="dn1,dn2" />
 7     </schema>
 8     <dataNode name="dn1" dataHost="localhost1" database="db01" />
 9     <dataNode name="dn2" dataHost="localhost1" database="db02" />
10     <dataHost name="localhost1" maxCon="1000" minCon="10" balance="0"
11               writeType="0" dbType="mysql" dbDriver="native" switchType="1"  slaveThreshold="100">
12         <heartbeat>select user()</heartbeat>
13         <writeHost host="hostS1" url="localhost:3306" user="root"
14                    password="jamesmsw" />
15     </dataHost>
16     
17 </mycat:schema>

這里配置好兩個數據庫節點dn1,dn2對應的就是這前面建立的數據庫db02,db03.

這樣數據庫和Mycat就設置好了,我們可以測試一下,向兩個庫中插入一些數據:

這是db01的數據,共40條.

這是db02中的數據,共8條.

這是mycat的邏輯庫james中的數據,可以看到,包含了所有的db01和db02的數據。

再來試試MyCat的注解:

在mycat的邏輯庫TESTDB中分別執行以下語句:

mysql> select count(*) from bom;
/*!mycat: datanode=dn1*/select count(*) from bom;
/*!mycat: datanode=dn2*/select count(*) from bom;

可以看到,注解實實在在地把SQL語句路由到對應的數據庫中去執行了,而不加注解的SQL則在整個邏輯庫上執行。

MyBatis設置插件攔截器

MyBatis要使用MyCat很方便,SpringBoot下,只需要將對應的url改成MyCat的端口就行了。

1 spring.thymeleaf.mode=LEGACYHTML5
2 spring.datasource.url=jdbc:mysql://localhost:8066/james?serverTimezone=GMT
3 spring.datasource.username=root
4 spring.datasource.password=james
5 spring.datasource.driver-class-name=com.mysql.jdbc.Driver
6 mybatis.config-location=classpath:mybatis.xml

MyBatis 允許你在已映射語句執行過程中的某一點進行攔截調用。

默認情況下,MyBatis 允許使用插件來攔截的方法調用包括:

  • Executor (update, query, flushStatements, commit, rollback, getTransaction, close, isClosed)
  • ParameterHandler (getParameterObject, setParameters)
  • ResultSetHandler (handleResultSets, handleOutputParameters)
  • StatementHandler (prepare, parameterize, batch, update, query)

這些類中方法的細節可以通過查看每個方法的簽名來發現,或者直接查看 MyBatis 的發行包中的源代碼。

假設你想做的不僅僅是監控方法的調用,那么你應該很好的了解正在重寫的方法的行為。

因為如果在試圖修改或重寫已有方法的行為的時候,你很可能在破壞 MyBatis 的核心模塊。 這些都是更低層的類和方法,所以使用插件的時候要特別當心。

通過 MyBatis 提供的強大機制,使用插件是非常簡單的,只需實現 Interceptor 接口,並指定了想要攔截的方法簽名即可。

在這里為了實現SQL的改造增加注解,Executor通過調度StatementHandler來完成查詢的過程,通過調度它的prepare方法預編譯SQL,因此我們可以攔截StatementHandler的prepare方法,在此之前完成SQL的重新編寫。

 1 package org.apache.ibatis.executor.statement;
 2  
 3 import java.sql.Connection;
 4 import java.sql.SQLException;
 5 import java.sql.Statement;
 6 import java.util.List;
 7 import org.apache.ibatis.cursor.Cursor;
 8 import org.apache.ibatis.executor.parameter.ParameterHandler;
 9 import org.apache.ibatis.mapping.BoundSql;
10 import org.apache.ibatis.session.ResultHandler;
11  
12 public interface StatementHandler {
13     Statement prepare(Connection var1, Integer var2) throws SQLException;
14  
15     void parameterize(Statement var1) throws SQLException;
16  
17     void batch(Statement var1) throws SQLException;
18  
19     int update(Statement var1) throws SQLException;
20  
21     <E> List<E> query(Statement var1, ResultHandler var2) throws SQLException;
22  
23     <E> Cursor<E> queryCursor(Statement var1) throws SQLException;
24  
25     BoundSql getBoundSql();
26  
27     ParameterHandler getParameterHandler();
28 }

以上的任何方法都可以攔截。從接口定義而言,Prepare方法有一個參數Connection對象,因此按如下代碼設計攔截器:

 1 package com.sanshengshui.multitenant.interceptor;
 2 
 3 import com.sanshengshui.multitenant.utils.SessionUtil;
 4 import org.apache.ibatis.executor.statement.StatementHandler;
 5 import org.apache.ibatis.plugin.*;
 6 import org.apache.ibatis.reflection.MetaObject;
 7 import org.apache.ibatis.reflection.SystemMetaObject;
 8 
 9 import java.sql.Connection;
10 import java.util.Properties;
11 
12 @Intercepts(value = {
13         @Signature(type = StatementHandler.class,
14                 method = "prepare",
15                 args = {Connection.class,Integer.class})})
16 public class MyInterceptor implements Interceptor {
17     private static final String preState="/*!mycat:datanode=";
18     private static final String afterState="*/";
19 
20     @Override
21     public Object intercept(Invocation invocation) throws Throwable {
22         StatementHandler statementHandler=(StatementHandler)invocation.getTarget();
23         MetaObject metaStatementHandler=SystemMetaObject.forObject(statementHandler);
24         Object object=null;
25         //分離代理對象鏈
26         while(metaStatementHandler.hasGetter("h")){
27             object=metaStatementHandler.getValue("h");
28             metaStatementHandler=SystemMetaObject.forObject(object);
29         }
30         statementHandler=(StatementHandler)object;
31         String sql=(String)metaStatementHandler.getValue("delegate.boundSql.sql");
32         /*
33         String node=(String)TestController.threadLocal.get();
34         */
35         String node=(String) SessionUtil.getSession().getAttribute("corp");
36         if(node!=null) {
37             sql = preState + node + afterState + sql;
38         }
39 
40         System.out.println("sql is "+sql);
41         metaStatementHandler.setValue("delegate.boundSql.sql",sql);
42         Object result = invocation.proceed();
43         System.out.println("Invocation.proceed()");
44         return result;
45     }
46 
47     @Override
48     public Object plugin(Object target) {
49 
50         return Plugin.wrap(target, this);
51     }
52 
53     @Override
54     public void setProperties(Properties properties) {
55         String prop1 = properties.getProperty("prop1");
56         String prop2 = properties.getProperty("prop2");
57         System.out.println(prop1 + "------" + prop2);
58     }
59 }

簡單說明一下:

intercept 真個是插件真正運行的方法,它將直接覆蓋掉你真實攔截對象的方法。里面有一個Invocation對象,利用它可以調用你原本要攔截的對象的方法

plugin 它是一個生成動態代理對象的方法,

setProperties 它是允許你在使用插件的時候設置參數值。

1 @Intercepts(value = {
2         @Signature(type = StatementHandler.class,
3                 method = "prepare",
4                 args = {Connection.class,Integer.class})})

這段說明了要攔截的目標類和方法以及參數。

StatementHandler是一個借口,真實的對象是RoutingStatementHandler,但它不是真實的服務對象,里面的delegate是StatementHandler中真實的StatementHandler實現的類,有多種,它里面的boundSql中存儲着SQL語句。具體的可以參考MyBatis的源碼。

MetaObject是一個工具類,由於MyBatis四大對象提供的public設置參數的方法很少,難以通過自身得到相關屬性信息,但是有個MetaObject這個工具類就可以通過其他的技術手段來讀取和修改這些重要對象的屬性。

SessionUtil的getSession方法是用來獲取之前用戶登錄時獲得的記錄在session中的corp信息,根據這個信息拼裝SQL注解達到多租戶的目的。

Interceptor寫好后,寫入到mybatis.xml的plugin中

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE configuration
        PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-config.dtd">

<configuration>
    <settings>
        <!-- 打印查詢語句 -->
        <setting name="logImpl" value="STDOUT_LOGGING" />
    </settings>

    <typeAliases>
        <typeAlias alias="BomDO" type="com.sanshengshui.multitenant.pojo.BomDO"/>

    </typeAliases>

    <plugins>
        <plugin interceptor="com.sanshengshui.multitenant.interceptor.MyInterceptor">
        </plugin>
    </plugins>
</configuration>

實際運行

寫一個實體類.

  1 package com.sanshengshui.multitenant.pojo;
  2 
  3 import lombok.Data;
  4 
  5 import java.io.Serializable;
  6 
  7 @Data
  8 public class BomDO implements Serializable {
  9     private static final long serialVersionUID = 1L;
 10     //物料編碼
 11     private Long cateId;
 12     //父物料ID,一級物料為0
 13     private Long parentId;
 14     //物料編碼
 15     private String cateCode;
 16     //物料名稱
 17     private String name;
 18     //計量單位
 19     private String unit;
 20     //規格
 21     private String specify;
 22     //狀態(0:開啟 1:禁用)
 23     private Integer status;
 24     //使用數量
 25     private Double usedCount;
 26     //描述
 27     private String description;
 28     //2=自制件,1=采購件
 29     private Integer property;
 30 
 31     public Long getCateId() {
 32         return cateId;
 33     }
 34 
 35     public void setCateId(Long cateId) {
 36         this.cateId = cateId;
 37     }
 38 
 39     public Long getParentId() {
 40         return parentId;
 41     }
 42 
 43     public void setParentId(Long parentId) {
 44         this.parentId = parentId;
 45     }
 46 
 47     public String getCateCode() {
 48         return cateCode;
 49     }
 50 
 51     public void setCateCode(String cateCode) {
 52         this.cateCode = cateCode;
 53     }
 54 
 55     public String getName() {
 56         return name;
 57     }
 58 
 59     public void setName(String name) {
 60         this.name = name;
 61     }
 62 
 63     public String getUnit() {
 64         return unit;
 65     }
 66 
 67     public void setUnit(String unit) {
 68         this.unit = unit;
 69     }
 70 
 71     public String getSpecify() {
 72         return specify;
 73     }
 74 
 75     public void setSpecify(String specify) {
 76         this.specify = specify;
 77     }
 78 
 79     public Integer getStatus() {
 80         return status;
 81     }
 82 
 83     public void setStatus(Integer status) {
 84         this.status = status;
 85     }
 86 
 87     public Double getUsedCount() {
 88         return usedCount;
 89     }
 90 
 91     public void setUsedCount(Double usedCount) {
 92         this.usedCount = usedCount;
 93     }
 94 
 95     public String getDescription() {
 96         return description;
 97     }
 98 
 99     public void setDescription(String description) {
100         this.description = description;
101     }
102 
103     public Integer getProperty() {
104         return property;
105     }
106 
107     public void setProperty(Integer property) {
108         this.property = property;
109     }
110 }

寫一個mapper

 1 package com.sanshengshui.multitenant.mapper;
 2 
 3 import com.sanshengshui.multitenant.pojo.BomDO;
 4 import org.apache.ibatis.annotations.Mapper;
 5 
 6 import java.util.List;
 7 import java.util.Map;
 8 
 9 @Mapper
10 public interface BomMapper {
11 
12     List<BomDO> list(Map<String, Object> map);
13 
14     int count(Map<String, Object> map);
15     
16 }package com.sanshengshui.multitenant.mapper;
17 
18 import com.sanshengshui.multitenant.pojo.BomDO;
19 import org.apache.ibatis.annotations.Mapper;
20 
21 import java.util.List;
22 import java.util.Map;
23 
24 @Mapper
25 public interface BomMapper {
26 
27     List<BomDO> list(Map<String, Object> map);
28 
29     int count(Map<String, Object> map);
30     
31 }

以及對應的BomMapper.xml,配置好sql語句。

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">

<mapper namespace="com.sanshengshui.multitenant.mapper.BomMapper">

    <select id="get" resultType="com.sanshengshui.multitenant.pojo.BomDO">
        select `cate_id`,`parent_id`,`cate_code`,`name`,`unit`,`used_count`,`specify`,`property`,`status`,`description` from bom where cate_id = #{value}
    </select>
    
    <select id="list" resultType="com.sanshengshui.multitenant.pojo.BomDO">
        select `cate_id`,`parent_id`,`cate_code`,`name`,`unit`,`used_count`,`specify`,`property`,`status`,`description` from bom
        <where>  
                    <if test="cateId != null and cateId != ''"> and cate_id = #{cateId} </if>
                    <if test="parentId != null and parentId != ''"> and parent_id = #{parentId} </if>
                    <if test="cateCode != null and cateCode != ''"> and cate_code = #{cateCode} </if>
                    <if test="name != null and name != ''"> and name = #{name} </if>
                    <if test="unit != null and unit != ''"> and unit = #{unit} </if>
                    <if test="usedCount != null and usedCount != ''"> and used_count = #{usedCount} </if>
                    <if test="specify != null and specify != ''"> and specify = #{specify} </if>
                    <if test="property != null and property != ''"> and property = #{property} </if>
                    <if test="status != null and status != ''"> and status = #{status} </if>
                    <if test="description != null and description != ''"> and description = #{description} </if>
                  </where>
        <choose>
            <when test="sort != null and sort.trim() != ''">
                order by ${sort} ${order}
            </when>
            <otherwise>
                order by cate_id desc
            </otherwise>
        </choose>
        <if test="offset != null and limit != null">
            limit #{offset}, #{limit}
        </if>
    </select>
    <!-- 為了保證mycat分庫的操作會進行,同一查詢會重新執行而不是在sqlSession中查找,需要加上flushCache="true"-->
     <select id="count" flushCache="true" resultType="int">
        select count(*) from bom
         <where>  
                    <if test="cateId != null and cateId != ''"> and cate_id = #{cateId} </if>
                    <if test="parentId != null and parentId != ''"> and parent_id = #{parentId} </if>
                    <if test="cateCode != null and cateCode != ''"> and cate_code = #{cateCode} </if>
                    <if test="name != null and name != ''"> and name = #{name} </if>
                    <if test="unit != null and unit != ''"> and unit = #{unit} </if>
                    <if test="usedCount != null and usedCount != ''"> and used_count = #{usedCount} </if>
                    <if test="specify != null and specify != ''"> and specify = #{specify} </if>
                    <if test="property != null and property != ''"> and property = #{property} </if>
                    <if test="status != null and status != ''"> and status = #{status} </if>
                    <if test="description != null and description != ''"> and description = #{description} </if>
                  </where>
    </select>
     
    <insert id="save" parameterType="com.sanshengshui.multitenant.pojo.BomDO" useGeneratedKeys="true" keyProperty="cateId">
        insert into bom
        (
            `parent_id`, 
            `cate_code`, 
            `name`, 
            `unit`, 
            `used_count`, 
            `specify`, 
            `property`, 
            `status`, 
            `description`
        )
        values
        (
            #{parentId}, 
            #{cateCode}, 
            #{name}, 
            #{unit}, 
            #{usedCount}, 
            #{specify}, 
            #{property}, 
            #{status}, 
            #{description}
        )
    </insert>

</mapper>

這里還是來測試count方法。

寫一個controller

 1 package com.sanshengshui.multitenant.controller;
 2 
 3 import com.sanshengshui.multitenant.mapper.BomMapper;
 4 import com.sanshengshui.multitenant.pojo.BomDO;
 5 import com.sanshengshui.multitenant.utils.SessionUtil;
 6 import org.springframework.beans.factory.annotation.Autowired;
 7 import org.springframework.stereotype.Controller;
 8 import org.springframework.web.bind.annotation.GetMapping;
 9 import org.springframework.web.bind.annotation.PathVariable;
10 import org.springframework.web.bind.annotation.ResponseBody;
11 
12 import javax.servlet.http.HttpSession;
13 import java.util.HashMap;
14 import java.util.List;
15 import java.util.Map;
16 
17 @Controller
18 public class TestController {
19 
20     @Autowired
21     BomMapper bomMapper;
22 
23     //簡化,直接通過這里設置session
24     @GetMapping("/set/{sess}")
25     @ResponseBody
26     public Object setSession(@PathVariable("sess") String sess){
27         HttpSession httpSession= SessionUtil.getSession();
28         httpSession.setAttribute("corp",sess);
29         return "ok";
30     }
31 
32     @ResponseBody
33     @GetMapping("/list")
34     public List<BomDO> list(){
35         Map<String, Object> query = new HashMap<>(16);
36         List<BomDO> bomList = bomMapper.list(query);
37         return bomList;
38     }
39     
40     @GetMapping("/count")
41     @ResponseBody
42     public Object getCount(){
43         //要測試的方法
44         Map<String, Object> map = new HashMap<String, Object>();
45         return bomMapper.count(map);
46     }   
47 }

首先通過localhost:8080/set/{sess}設置session,假設session設置為dn1.

瀏覽器中輸入localhost:8080/set/dn1.

之后,輸入localhost:8080/count.

結果如下:

來看下打印的sql語句:

可以看到,SQL注解已經成功添加進去了。

在設置session為dn2

結果如下。

打印的sql語句:

 

附件工程代碼

作者github賬號:https://github.com/sanshengshui


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM