本文已參與好文召集令活動,點擊查看:後端、大前端雙賽道投稿,2萬元獎池等你挑戰!php
MyBatis Plus是在 MyBatis 的基礎上只作加強不作改變,能夠簡化開發,提升效率.css
BaseMapper接口中通用的 CRUD 方法: 簡單的數據庫操做不須要在 EmployeeMapper 接口中定義任何方法,也沒有在配置文件中編寫SQL語句,而是經過繼承BaseMapper接口得到通用的的增刪改查方法,複雜的SQL也可使用條件構造器拼接.不過複雜的業務需求仍是要編寫SQL語句的,流程和MyBatis同樣.html
<dependency>
<groupId>org.apache.velocity</groupId>
<artifactId>velocity-engine-core</artifactId>
<version>2.0</version>
<scope>test</scope>
</dependency>
複製代碼
public class MysqlGenerator {
private static final String PACKAGE_NAME = "cn.lqdev.learning.springboot.chapter9";
private static final String MODULE_NAME = "biz";
private static final String OUT_PATH = "D:\\develop\\code";
private static final String AUTHOR = "oKong";
private static final String DRIVER = "com.mysql.jdbc.Driver";
private static final String URL = "jdbc:mysql://127.0.0.1:3306/learning?useUnicode=true&characterEncoding=UTF-8";
private static final String USER_NAME = "root";
private static final String PASSWORD = "123456";
/** * <p> * MySQL 生成演示 * </p> */
public static void main(String[] args) {
// 自定義須要填充的字段
List<TableFill> tableFillList = new ArrayList<TableFill>();
// 代碼生成器
AutoGenerator mpg = new AutoGenerator().setGlobalConfig(
// 全局配置
new GlobalConfig().setOutputDir(OUT_PATH)// 輸出目錄
.setFileOverride(true)// 是否覆蓋文件
.setActiveRecord(true)// 開啓 activeRecord 模式
.setEnableCache(false)// XML 二級緩存
.setBaseResultMap(false)// XML ResultMap
.setBaseColumnList(true)// XML columList
.setAuthor(AUTHOR)
// 自定義文件命名,注意 %s 會自動填充表實體屬性!
.setXmlName("%sMapper").setMapperName("%sDao")
// .setServiceName("MP%sService")
// .setServiceImplName("%sServiceDiy")
// .setControllerName("%sAction")
).setDataSource(
// 數據源配置
new DataSourceConfig().setDbType(DbType.MYSQL)// 數據庫類型
.setTypeConvert(new MySqlTypeConvert() {
// 自定義數據庫表字段類型轉換【可選】
@Override
public DbColumnType processTypeConvert(String fieldType) {
System.out.println("轉換類型:" + fieldType);
// if ( fieldType.toLowerCase().contains( "tinyint" ) ) {
// return DbColumnType.BOOLEAN;
// }
return super.processTypeConvert(fieldType);
}
}).setDriverName(DRIVER).setUsername(USER_NAME).setPassword(PASSWORD).setUrl(URL))
.setStrategy(
// 策略配置
new StrategyConfig()
// .setCapitalMode(true)// 全局大寫命名
.setDbColumnUnderline(true)// 全局下劃線命名
// .setTablePrefix(new String[]{"unionpay_"})// 此處能夠修改成您的表前綴
.setNaming(NamingStrategy.underline_to_camel)// 表名生成策略
// .setInclude(new String[] {"citycode_org"}) // 須要生成的表
// .setExclude(new String[]{"test"}) // 排除生成的表
// 自定義實體,公共字段
// .setSuperEntityColumns(new String[]{"test_id"})
.setTableFillList(tableFillList)
// 自定義實體父類
// .setSuperEntityClass("com.baomidou.demo.common.base.BsBaseEntity")
// // 自定義 mapper 父類
// .setSuperMapperClass("com.baomidou.demo.common.base.BsBaseMapper")
// // 自定義 service 父類
// .setSuperServiceClass("com.baomidou.demo.common.base.BsBaseService")
// // 自定義 service 實現類父類
// .setSuperServiceImplClass("com.baomidou.demo.common.base.BsBaseServiceImpl")
// 自定義 controller 父類
// .setSuperControllerClass("com.baomidou.demo.TestController")
// 【實體】是否生成字段常量(默認 false)
// public static final String ID = "test_id";
.setEntityColumnConstant(true)
// 【實體】是否爲構建者模型(默認 false)
// public User setName(String name) {this.name = name; return this;}
.setEntityBuilderModel(true)
// 【實體】是否爲lombok模型(默認 false)<a href="https://projectlombok.org/">document</a>
.setEntityLombokModel(true)
// Boolean類型字段是否移除is前綴處理
// .setEntityBooleanColumnRemoveIsPrefix(true)
// .setRestControllerStyle(true)
// .setControllerMappingHyphenStyle(true)
).setPackageInfo(
// 包配置
new PackageConfig().setModuleName(MODULE_NAME).setParent(PACKAGE_NAME)// 自定義包路徑
.setController("controller")// 這裏是控制器包名,默認 web
.setXml("mapper").setMapper("dao")
).setCfg(
// 注入自定義配置,能夠在 VM 中使用 cfg.abc 設置的值
new InjectionConfig() {
@Override
public void initMap() {
Map<String, Object> map = new HashMap<String, Object>();
map.put("abc", this.getConfig().getGlobalConfig().getAuthor() + "-mp");
this.setMap(map);
}
}.setFileOutConfigList(
Collections.<FileOutConfig>singletonList(new FileOutConfig("/templates/mapper.xml.vm") {
// 自定義輸出文件目錄
@Override
public String outputFile(TableInfo tableInfo) {
return OUT_PATH + "/xml/" + tableInfo.getEntityName() + "Mapper.xml";
}
})))
.setTemplate(
// 關閉默認 xml 生成,調整生成 至 根目錄
new TemplateConfig().setXml(null)
// 自定義模板配置,模板能夠參考源碼 /mybatis-plus/src/main/resources/template 使用 copy
// 至您項目 src/main/resources/template 目錄下,模板名稱也可自定義以下配置:
// .setController("...");
// .setEntity("...");
// .setMapper("...");
// .setXml("...");
// .setService("...");
// .setServiceImpl("...");
);
// 執行生成
mpg.execute();
}
}
複製代碼
@RunWith(SpringRunner.class)
//SpringBootTest 是springboot 用於測試的註解,可指定啓動類或者測試環境等,這裏直接默認。
@SpringBootTest
@Slf4j
public class GeneralTest {
@Autowired
IUserService userService;
@Test
public void testInsert() {
User user = new User();
user.setCode("001");
user.setName("okong-insert");
//默認的插入策略爲:FieldStrategy.NOT_NULL,即:判斷 null
//對應在mapper.xml時寫法爲:<if test="field!=null">
//這個能夠修改的,設置字段的@TableField(strategy=FieldStrategy.NOT_EMPTY)
//因此這個時候,爲null的字段是不會更新的,也能夠開啓性能插件,查看sql語句就能夠知道
userService.insert(user);
//新增全部字段,
userService.insertAllColumn(user);
log.info("新增結束");
}
@Test
public void testUpdate() {
User user = new User();
user.setCode("101");
user.setName("oKong-insert");
//這就是ActiveRecord的功能
user.insert();
//也能夠直接 userService.insert(user);
//更新
User updUser = new User();
updUser.setId(user.getId());
updUser.setName("okong-upd");
updUser.updateById();
log.info("更新結束");
}
@Test
public void testDelete() {
User user = new User();
user.setCode("101");
user.setName("oKong-delete");
user.insert();
//刪除
user.deleteById();
log.info("刪除結束");
}
@Test
public void testSelect() {
User user = new User();
user.setCode("201");
user.setName("oKong-selecdt");
user.insert();
log.info("查詢:{}",user.selectById());
}
}
複製代碼
對於通用代碼如何注入的,可查看com.baomidou.mybatisplus.mapper.AutoSqlInjector類,這個就是注入通用的CURD方法的類.前端
條件構造器主要提供了實體包裝器,用於處理SQL語句拼接,排序,實體參數查詢:使用的是數據庫字段,不是Java屬性java
SQL條件拼接測試類ConditionTestnode
@RunWith(SpringRunner.class)
//SpringBootTest 是springboot 用於測試的註解,可指定啓動類或者測試環境等,這裏直接默認。
@SpringBootTest
@Slf4j
public class ConditionTest {
@Autowired
IUserService userService;
@Test
public void testOne() {
User user = new User();
user.setCode("701");
user.setName("okong-condition");
user.insert();
EntityWrapper<User> qryWrapper = new EntityWrapper<>();
qryWrapper.eq(User.CODE, user.getCode());
qryWrapper.eq(User.NAME, user.getName());
//也能夠直接
// qryWrapper.setEntity(user);
//打印sql語句
System.out.println(qryWrapper.getSqlSegment());
//設置select 字段 即:select code,name from
qryWrapper.setSqlSelect(User.CODE,User.NAME);
System.out.println(qryWrapper.getSqlSelect());
//查詢
User qryUser = userService.selectOne(qryWrapper);
System.out.println(qryUser);
log.info("拼接一結束");
}
@Test
public void testTwo() {
User user = new User();
user.setCode("702");
user.setName("okong-condition");
user.insert();
EntityWrapper<User> qryWrapper = new EntityWrapper<>();
qryWrapper.where("code = {0}", user.getCode())
.and("name = {0}",user.getName())
.andNew("status = 0");
System.out.println(qryWrapper.getSqlSegment());
//等等很複雜的。
//複雜的建議直接寫在xml裏面了,要是非動態的話 比較xml一眼看得懂呀
//查詢
User qryUser = userService.selectOne(qryWrapper);
System.out.println(qryUser);
log.info("拼接二結束");
}
}
複製代碼
MyBatis Plus提供的條件構造方法com.baomidou.mybatisplus.mapper.Wrapper python
UserDao.java加入接口方法:mysql
/** * * @param rowBounds 分頁對象 直接傳入page便可 * @param wrapper 條件構造器 * @return */
List<User> selectUserWrapper(RowBounds rowBounds, @Param("ew") Wrapper<User> wrapper);
複製代碼
UserMapper.xml加入對應的xml節點:linux
<!-- 條件構造器形式 -->
<select id="selectUserWrapper" resultType="user">
SELECT
<include refid="Base_Column_List" />
FROM USER
<where>
${ew.sqlSegment}
</where>
</select>
複製代碼
自定義SQL使用條件構造器測試類:git
@Test
public void testCustomSql() {
User user = new User();
user.setCode("703");
user.setName("okong-condition");
user.insert();
EntityWrapper<User> qryWrapper = new EntityWrapper<>();
qryWrapper.eq(User.CODE, user.getCode());
Page<User> pageUser = new Page<>();
pageUser.setCurrent(1);
pageUser.setSize(10);
List<User> userlist = userDao.selectUserWrapper(pageUser, qryWrapper);
System.out.println(userlist.get(0));
log.info("自定義sql結束");
}
複製代碼
UserDao.java:
/** * * @param rowBounds 分頁對象 直接傳入page便可 * @param wrapper 條件構造器 * @return */
List<User> selectUserWrapper(RowBounds rowBounds, @Param("ew") Wrapper<User> wrapper);
複製代碼
UserMapper.xml:
<!-- 條件構造器形式 -->
<select id="selectUserWrapper" resultType="user">
SELECT
<include refid="Base_Column_List" />
FROM USER
<where>
${ew.sqlSegment}
</where>
</select>
複製代碼
查詢方式 | 使用說明 |
---|---|
setSqlSelect | 設置SELECT查詢字段 |
where | WHERE語句,拼接+WHERE條件 |
and | AND語句,拼接+AND 字段=值 |
andNew | AND 語句,拼接+AND(字段=值) |
or | OR 語句,拼接+OR 字段=值 |
orNew | OR 語句,拼接+OR(字段=值) |
eq | 等於= |
allEq | 基於map內容等於= |
ne | 不等於<> |
gt | 大於> |
ge | 大於等於>= |
lt | 小於< |
le | 小於等於<= |
like | 模糊查詢 LIKE |
notLike | 模糊查詢NOT LIKE |
in | IN 查詢 |
notIn | NOT IN查詢 |
isNull | NULL值查詢 |
isNotNull | IS NOT NULL |
groupBy | 分組GROUP BY |
having | HAVING關鍵詞 |
orderBy | 排序ORDER BY |
orderAsc | 排序ASC ORDER BY |
orderDesc | 排序DESC ORDER BY |
exists | EXISTS條件語句 |
notExists | NOT EXISTS條件語句 |
between | BETWEEN條件語句 |
notBetween | NOT BETWEEN條件語句 |
addFilter | 自由拼接SQL |
last | 拼接在最後 |
##### 自定義SQL語句 | |
在多表關聯時,條件構造器和通用CURD都沒法知足時,能夠編寫SQL語句進行擴展.這些都是mybatis的用法.首先改造UserDao接口,有兩種方式: |
@Select("SELECT * FROM USER WHERE CODE = #{userCode}")
List<User> selectUserCustomParamsByAnno(@Param("userCode")String userCode);
複製代碼
List<User> selectUserCustomParamsByXml(@Param("userCode")String userCode);
複製代碼
UserMapper.xml新增一個節點:
<!-- 因爲設置了別名:typeAliasesPackage=cn.lqdev.learning.mybatisplus.samples.biz.entity,因此resultType能夠不寫全路徑了。 -->
<select id="selectUserCustomParamsByXml" resultType="user">
SELECT
<include refid="Base_Column_List"/>
FROM USER
WHERE CODE = #{userCode}
</select>
複製代碼
自定義SQL語句測試類CustomSqlTest:
@RunWith(SpringRunner.class)
//SpringBootTest 是springboot 用於測試的註解,可指定啓動類或者測試環境等,這裏直接默認。
@SpringBootTest
@Slf4j
public class CustomSqlTest {
@Autowired
UserDao userDao;
@Test
public void testCustomAnno() {
User user = new User();
user.setCode("901");
user.setName("okong-sql");
user.insert();
List<User> userlist = userDao.selectUserCustomParamsByAnno(user.getCode());
//因爲新增的 確定不爲null 故不判斷了。
System.out.println(userlist.get(0).toString());
log.info("註解形式結束------");
}
@Test
public void testCustomXml() {
User user = new User();
user.setCode("902");
user.setName("okong-sql");
user.insert();
List<User> userlist = userDao.selectUserCustomParamsByXml(user.getCode());
//因爲新增的 確定不爲null 故不判斷了。
System.out.println(userlist.get(0).toString());
log.info("xml形式結束------");
}
}
複製代碼
注意: 在使用spring-boot-maven-plugin插件打包成springboot運行jar時,須要注意:因爲springboot的jar掃描路徑方式問題,會致使別名的包未掃描到,因此這個只須要把mybatis默認的掃描設置爲Springboot的VFS實現.修改spring-mybatis.xml文件:
<!--mybatis-->
<bean id="sqlSessionFactory" class="com.baomidou.mybatisplus.spring.MybatisSqlSessionFactoryBean">
<property name="dataSource" ref="dataSource"/>
<!-- 自動掃描mapper.xml文件,支持通配符 -->
<property name="mapperLocations" value="classpath:mapper/**/*.xml"/>
<!-- 配置文件,好比參數配置(是否啓動駝峯等)、插件配置等 -->
<property name="configLocation" value="classpath:mybatis/mybatis-config.xml"/>
<!-- 啓用別名,這樣就無需寫全路徑類名了,具體可自行查閱資料 -->
<property name="typeAliasesPackage" value="cn.lqdev.learning.mybatisplus.samples.biz.entity"/>
<!-- MP 全局配置注入 -->
<property name="globalConfig" ref="globalConfig"/>
<!-- 設置vfs實現,避免路徑掃描問題 -->
<property name="vfs" value="com.baomidou.mybatisplus.spring.boot.starter.SpringBootVFS"></property>
</bean>
複製代碼
mybatis的插件機制使用只須要註冊便可
<plugins>
<!-- SQL 執行性能分析,開發環境使用,線上不推薦。 -->
<plugin interceptor="com.baomidou.mybatisplus.plugins.PerformanceInterceptor"></plugin>
<!-- 分頁插件配置 -->
<plugin interceptor="com.baomidou.mybatisplus.plugins.PaginationInterceptor"></plugin>
</plugins>
複製代碼
@RunWith(SpringRunner.class)
//SpringBootTest 是springboot 用於測試的註解,可指定啓動類或者測試環境等,這裏直接默認。
@SpringBootTest
@Slf4j
public class PluginTest {
@Autowired
IUserService userService;
@Test
public void testPagination() {
Page<User> page = new Page<>();
//每頁數
page.setSize(10);
//當前頁碼
page.setCurrent(1);
//無條件時
Page<User> pageList = userService.selectPage(page);
System.out.println(pageList.getRecords().get(0));
//新增數據 避免查詢不到數據
User user = new User();
user.setCode("801");
user.setName("okong-Pagination");
user.insert();
//加入條件構造器
EntityWrapper<User> qryWapper = new EntityWrapper<>();
//這裏也能直接設置 entity 這是條件就是entity的非空字段值了
// qryWapper.setEntity(user);
//這裏建議直接用 常量
// qryWapper.eq(User.CODE, user.getCode());
pageList = userService.selectPage(page, qryWapper);
System.out.println(pageList.getRecords().get(0));
log.info("分頁結束");
}
}
複製代碼
Time:4 ms - ID:cn.lqdev.learning.mybatisplus.samples.biz.dao.UserDao.selectPage
Execute SQL: SELECT id AS id,code,`name`,`status`,gmt_create AS gmtCreate,gmt_modified AS gmtModified FROM user WHERE id=1026120705692434433 AND code='801' AND `name`='okong-Pagination' LIMIT 0,10
複製代碼
一般,每一個公司都有本身的表定義,在《阿里巴巴Java開發手冊》中,就強制規定表必備三字段:id,gmt_create,gmt_modified.因此一般咱們都會寫個公共的攔截器去實現自動填充好比建立時間和更新時間的,無需開發人員手動設置.而在MP中就提供了這麼一個公共字段自動填充功能
==注意==能夠在代碼生成器裏面配置規則的,可自動配置
/** * 建立時間 */
@TableField(fill=FieldFill.INSERT)
private Date gmtCreate;
/** * 修改時間 */
@TableField(fill=FieldFill.INSERT_UPDATE)
private Date gmtModified;
複製代碼
public class MybatisObjectHandler extends MetaObjectHandler{
@Override
public void insertFill(MetaObject metaObject) {
//新增時填充的字段
setFieldValByName("gmtCreate", new Date(), metaObject);
setFieldValByName("gmtModified", new Date(), metaObject);
}
@Override
public void updateFill(MetaObject metaObject) {
//更新時 須要填充字段
setFieldValByName("gmtModified", new Date(), metaObject);
}
}
複製代碼
<bean id="globalConfig" class="com.baomidou.mybatisplus.entity.GlobalConfiguration">
<!-- AUTO->`0`("數據庫ID自增")QW INPUT->`1`(用戶輸入ID") ID_WORKER->`2`("全局惟一ID") UUID->`3`("全局惟一ID") -->
<property name="idType" value="2" />
<property name="metaObjectHandler" ref="mybatisObjectHandler"></property>
</bean>
<bean id="mybatisObjectHandler" class="cn.lqdev.learning.mybatisplus.samples.config.MybatisObjectHandler"/>
複製代碼
再新增或者修改時,對應時間就會進行更新:
Time:31 ms - ID:cn.lqdev.learning.mybatisplus.samples.biz.dao.UserDao.insert
Execute SQL: INSERT INTO user ( id, code, `name`, gmt_create,gmt_modified ) VALUES ( 1026135016838037506, '702', 'okong-condition', '2018-08-05 23:57:07.344','2018-08-05 23:57:07.344' )
複製代碼
配置參數 | 缺省值 | 說明 |
---|---|---|
name | 若是存在多個數據源,監控時能夠經過name屬性進行區分,若是沒有配置,將會生成一個名字:"DataSource-"+System.identityHashCode(this) | |
jdbcUrl | 鏈接數據庫的url,不一樣的數據庫url表示方式不一樣: mysql:jdbc:mysql://192.16.32.128:3306/druid2 oracle : jdbc:oracle:thin:@192.16.32.128:1521:druid2 |
|
username | 鏈接數據庫的用戶名 | |
password | 鏈接數據庫的密碼,密碼不出如今配置文件中能夠使用ConfigFilter | |
driverClassName | 根據jdbcUrl自動識別 | 能夠不配置,Druid會根據jdbcUrl自動識別dbType,選擇相應的driverClassName |
initialSize | 0 | 初始化時創建物理鏈接的個數. 初始化過程發生在:顯示調用init方法;第一次getConnection |
maxActive | 8 | 最大鏈接池數量 |
minIdle | 最小鏈接池數量 | |
maxWait | 獲取鏈接時最大等待時間,單位毫秒. 配置maxWait默認使用公平鎖等待機制,併發效率會降低.能夠配置useUnfairLock爲true使用非公平鎖 |
|
poolPreparedStatements | false | 是否緩存preparedStatement,即PSCache. PSCache可以提高對支持遊標的數據庫性能. 在Oracle中使用,在MySQL中關閉 |
maxOpenPreparedStatements | -1 | 要啓用PSCache,必須配置參數值>0,poolPreparedStatements自動觸發修改成true. Oracle中能夠配置數值爲100,Oracle中不會存在PSCache過多的問題 |
validationQuery | 用來檢測鏈接的是否爲有效SQL,要求是一個查詢語句 若是validationQuery=null,那麼testOnBorrow,testOnReturn,testWhileIdle都不會起做用 |
|
testOnBorrow | true | 申請鏈接時執行validationQuery檢測鏈接是否有效,會下降性能 |
testOnReturn | false | 歸還鏈接時執行validationQuery檢測鏈接是否有效,會下降性能 |
testWhileIdle | false | 申請鏈接時,空閒時間大於timeBetweenEvictionRunsMillis時,執行validationQuery檢測鏈接是否有效 不影響性能,保證安全性,建議配置爲true |
timeBetweenEvictionRunsMillis | Destroy線程會檢測鏈接的間隔時間 testWhileIdle的判斷依據 |
|
connectionInitSqls | 物理鏈接初始化時執行SQL | |
exceptionSorter | 根據dbType自動識別 | 當數據庫跑出不可恢復的異常時,拋棄鏈接 |
filters | 經過別名的方式配置擴展插件,屬性類型是字符串: 經常使用的插件: 監控統計用的filter:stat 日誌用的filter:log4j 防護sql注入的filter:wall |
|
proxyFilters | 類型是List<com.alibaba.druid.filter.Filter>,若是同時配置了filters和proxyFilters是組合關係,不是替換關係 | |
### Druid的架構 | ||
##### Druid數據結構 |
經過kafka消費,就是kafkaFireHose.
同時,實時節點的另一個模塊Plumer,用於Segment的生成,而且按照指定的週期,
將本週期內生成的全部數據塊合併成一個
複製代碼
1.實時節點生產出Segment文件,而且存到文件系統中
2.Segment文件的<MetaStore>存放到Mysql等其餘外部數據庫中
3.Master經過Mysql中的MetaStore,經過必定的規則,將Segment分配給屬於它的節點
4.歷史節點獲得Master發送的指令後會從文件系統中拉取屬於本身的Segment文件,而且經過zookeeper,告知集羣,本身提供了此塊Segment的查詢服務
5.實時節點丟棄Segment文件,而且聲明不在提供此塊文件的查詢服務
複製代碼
除了經過實時節點生產Segment文件以外,druid還提供了一組索引服務來攝入數據
索引服務包含一組組件,並以主從結構做爲架構方式,統治節點 Overload node爲主節點,中間管理者Middle Manager爲從節點
Middle Manager與Peon(苦工):
Middle Manager便是Overload node 的工做節點,負責接收Overload node分配的任務,
而後啓動相關的Peon來完成任務這種模式和yarn的架構比較相似
1.Overload node至關於Yarn的ResourceManager,負責資源管理和任務分配
2.Middle Manager至關於Yarn的NodeManager,負責管理獨立節點的資源,而且接收任務
3.Peon 至關於Yarn的Container,啓動在具體節點上具體任務的執行
複製代碼
-Zuul是一個基於JVM路由和服務端的負載均衡器,提供動態路由,監控,彈性,安全等邊緣服務的框架,至關因而設備和 Netflix 流應用的 Web 網站後端全部請求的前門
1.Zuul的過濾器之間沒有直接的相互通訊,他們之間經過一個RequestContext的靜態類來進行數據傳遞的。RequestContext類中有ThreadLocal變量來記錄每一個Request所須要傳遞的數據
2.Zuul的過濾器是由Groovy寫成,這些過濾器文件被放在Zuul Server上的特定目錄下面,Zuul會按期輪詢這些目錄,修改過的過濾器會動態的加載到Zuul Server中以便過濾請求使用
複製代碼
Zuul請求的生命週期詳細描述了各類類型的過濾器的執行順序
Zuul能夠經過加載動態過濾機制實現Zuul的功能:
1.首先根據Type獲取全部輸入該Type的filter:List<ZuulFilter> list
2.遍歷該list,執行每一個filter的處理邏輯:processZuulFilter(ZuulFilter filter)
3.RequestContext對每一個filter的執行情況進行記錄,應該留意,此處的執行狀態主要包括其執行時間、以及執行成功或者失敗,若是執行失敗則對異常封裝後拋出
4.到目前爲止,Zuul框架對每一個filter的執行結果都沒有太多的處理,它沒有把上一filter的執行結果交由下一個將要執行的filter,僅僅是記錄執行狀態,若是執行失敗拋出異常並終止執行
複製代碼
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
複製代碼
spring.redis.host=192.168.32.242
複製代碼
@Bean
@ConditionalOnMissingBean( name = {"redisTemplate"} )
public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) throws UnknownHostException {
RedisTemplate<Object, Object> template = new RedisTemplate();
template.setConnectionFactory(redisConnectionFactory);
return template;
}
複製代碼
@Configuration
public class MyRedisConfig {
@Bean
public RedisTemplate<Object, Employee> empRedisTemplate(RedisConnectionFactory redisConnectionFactory){
RedisTemplate<Object,Employee> redisTemplate=new RedisTemplate<Object,Employee>();
redisTemplate.setConnectionFactory(redisConnectionFactory);
Jackson2JsonRedisSerializer<Employee> serializer = new Jackson2JsonRedisSerializer<Employee>(Employee.class);
redisTemplate.setDefaultSerializer(serializer);
return redisTemplate;
}
}
複製代碼
Redis常見的數據類型:
String-字符串
List-列表
Set-集合
Hash-散列
ZSet-有序集合
redisTemplate.opsForValue()--String(字符串)
redisTemplate.opsForList()--List(列表)
redisTemplate.opsForSet()--Set(集合)
redisTemplate.opsForHash()--Hash(散列)
redisTemplate.opsForZSet()--ZSet(有序集合)
複製代碼
在RedisAutoConfiguration中:
@Bean
@ConditionalOnMissingBean
public StringRedisTemplate stringRedisTemplate(RedisConnectionFactory redisConnectionFactory) throws UnknownHostException {
StringRedisTemplate template = new StringRedisTemplate();
template.setConnectionFactory(redisConnectionFactory);
return template;
}
複製代碼
在StringRedisTemplate中:
public class StringRedisTemplate extends RedisTemplate<String, String> {
public StringRedisTemplate() {
this.setKeySerializer(RedisSerializer.string());
this.setValueSerializer(RedisSerializer.string());
this.setHashKeySerializer(RedisSerializer.string());
this.setHashValueSerializer(RedisSerializer.string());
}
public StringRedisTemplate(RedisConnectionFactory connectionFactory) {
this();
this.setConnectionFactory(connectionFactory);
this.afterPropertiesSet();
}
protected RedisConnection preProcessConnection(RedisConnection connection, boolean existingConnection) {
return new DefaultStringRedisConnection(connection);
}
}
複製代碼
Redis常見的數據類型:
String-字符串
List-列表
Set-集合
Hash-散列
ZSet-有序集合
stringRedisTemplate.opsForValue()--String(字符串)
stringRedisTemplate.opsForList()--List(列表)
stringRedisTemplate.opsForSet()--Set(集合)
stringRedisTemplate.opsForHash()--Hash(散列)
stringRedisTemplate.opsForZSet()--ZSet(有序集合)
複製代碼
fast paxos流程是在選舉過程當中,某Server首先向全部Server提議本身要成爲leader,當其它Server收到提議之後,解決epoch和zxid的衝突,並接受對方的提議,而後向對方發送接受提議完成的消息,重複這個流程,最後必定能選舉出Leader
Leader主要有三個功能:
Quartz框架中的核心類:
public class HelloJob implements Job{
public void execute(JobExecutionContext context) throws JobExecutionException {
//編寫咱們本身的業務邏輯
}
複製代碼
JobDetail jobDetail=JobBuilder.newJob(HelloJob.class).
withIdentity("myJob", "group1")
.build();
複製代碼
CronTrigger trigger = (CronTrigger) TriggerBuilder
.newTrigger()
.withIdentity("myTrigger", "group1") //建立一個標識符
.startAt(date)//何時開始觸發
//每秒鐘觸發一次任務
.withSchedule(CronScheduleBuilder.cronSchedule("* * * * * ? *"))
.build();
複製代碼
建立Scheduler有兩種方式
SchedulerFactory sfact=new StdSchedulerFactory();
Scheduler scheduler=sfact.getScheduler();
複製代碼
DiredtSchedulerFactory factory=DirectSchedulerFactory.getInstance();
Scheduler scheduler=factory.getScheduler();
複製代碼
Scheduler配置參數通常存儲在quartz.properties中,咱們能夠修改參數來配置相應的參數.經過調用getScheduler() 方法就能建立和初始化調度對象
在org.quartz這個包下,當咱們程序啓動的時候,它首先會到咱們的根目錄下查看是否配置了該資源文件,若是沒有就會到該包下讀取相應信息,當咱們咋實現更復雜的邏輯時,須要本身指定參數的時候,能夠本身配置參數來實現
org.quartz.scheduler.instanceName: DefaultQuartzScheduler
org.quartz.scheduler.rmi.export: false
org.quartz.scheduler.rmi.proxy: false
org.quartz.scheduler.wrapJobExecutionInUserTransaction: false
org.quartz.threadPool.class: org.quartz.simpl.SimpleThreadPool
org.quartz.threadPool.threadCount: 10
org.quartz.threadPool.threadPriority: 5
org.quartz.threadPool.threadsInheritContextClassLoaderOfInitializingThread: true
org.quartz.jobStore.misfireThreshold: 60000
org.quartz.jobStore.class: org.quartz.simpl.RAMJobStore
複製代碼
對事件進行監聽而且加入本身相應的業務邏輯,主要有如下三個監聽器分別對Job,Trigger,Scheduler進行監聽:
字段 | 容許值 | 容許特殊字符 |
---|---|---|
秒 | 0-59 | , - * / |
分 | 0-59 | , - * / |
小時 | 0-23 | , - * / |
日期 | 1-31 | , - * ? / L W C |
月份 | 1-12 | , - * / |
星期 | 0-7或SUN-SAT,0和7是SUN | , - * / |
特殊字符 | 含義 |
---|---|
, | 枚舉 |
- | 區間 |
* | 任意 |
/ | 步長 |
? | 日和星期的衝突匹配 |
L | 最後 |
w | 工做日 |
C | 與calendar聯繫後計算過的值 |
# | 星期: 4#2-第2個星期三 |
``` | |
second(秒),minute(分),hour(時),day of month(日),month(月),day of week(周幾) | |
0 * * * * MON-FRI | |
@Scheduled(cron="0 * * * * MON-FRI") | |
@Scheduled(cron="1,2,3 * * * * MON-FRI")-枚舉: , | |
@Scheduled(cron="0-15 * * * * MON-FRI")-區間: - | |
@Scheduled(cron="0/4 * * * * MON-FRI")-步長: / 從0開始,每4秒啓動一次 | |
cron="0 0/5 14,18 * * ?" 天天14點整和18點整,每隔5分鐘執行一次 | |
cron="0 15 10 ? * 1-6" 每月的週一至週六10:15分執行一次 | |
cron="0 0 2 ? * 6L" 每月的最後一個週六2點執行一次 | |
cron="0 0 2 LW * ?" 每月的最後一個工做日2點執行一次 | |
cron="0 0 2-4 ? * 1#1" 每月的第一個週一2點到4點,每一個整點執行一次 | |
``` |
該註解將一個controller類標註爲一個Swagger API. 在默認狀況下 ,Swagger core只會掃描解析具備 @Api註解的類,而忽略其它類別的資源,好比JAX-RS endpoints, Servlets等註解. 該註解的屬性有:
在指定接口路徑上,對一個操做或者http方法進行描述. 具備相同路徑的不一樣操做會被歸組爲同一個操做對象. 緊接着是不一樣的http請求方法註解和路徑組合構成一個惟一操做. 該註解的屬性有:
增長對參數的元信息說明,緊接着使用Http請求參數註解. 主要屬性有:
描述一個操做的可能返回結果. 當RESTful請求發生時,這個註解可用於描述全部可能的成功與錯誤碼.可使用也能夠不使用這個註解去描述操做返回類型. 但成功操做後的返回類型必須在 @ApiOperation中定義. 若是API具備不一樣的返回類型,那麼須要分別定義返回值,並將返回類型進行關聯. 可是Swagger不支持同一返回碼,多種返回類型的註解. 這個註解必須被包含在 @ApiResponses中:
註解 @ApiResponse的包裝類,數組結構. 即便須要使用一個 @ApiResponse註解,也須要將 @ApiResponse註解包含在註解 @ApiResponses內
對API的單一參數進行註解. 註解 @ApiParam須要同JAX-RS參數相綁定, 但這個 @ApiImplicitParam註解能夠以統一的方式定義參數列表,這是在Servlet和非JAX-RS環境下惟一的方式參數定義方式. 注意這個註解 @ApiImplicitParam必須被包含在註解 @ApiImplicitParams以內,能夠設置如下重要屬性:
註解 @ApiImplicitParam的容器類,以數組方式存儲
提供對Swagger model額外信息的描述. 在標註 @ApiOperation註解的操做內,全部類將自動introspected. 利用這個註解能夠作一些更詳細的model結構說明. 主要屬性值有:
對model屬性的註解,主要屬性值有:
< changeSet > 標籤的主要屬性有:
< changeSet > 有一個 < rollback > 子標籤,用來定義回滾語句:
<?xml version="1.0" encoding="utf-8"?>
<databaseChangeLog xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://www.liquibase.org/xml/ns/dbchangelog" xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-3.1.xsd">
<include file="logset-20160408/0001_authorization_init.sql" relativeToChangelogFile="true"/>
</databaseChangeLog>
複製代碼
<includeAll path="com/example/changelogs/"/>
複製代碼
java -jar liquibase.jar --driver=com.mysql.jdbc.Driver \
--classpath=./mysql-connector-java-5.1.29.jar \
--url=jdbc:mysql://127.0.0.1:3306/test \
--username=root --password=passwd \
diff \
--referenceUrl=jdbc:mysql://127.0.0.1:3306/authorization \
--referenceUsername=root --referencePassword=passwd
複製代碼
liquibase --driver=com.mysql.jdbc.Driver \
- classpath=./mysql-connector-java-5.1.29.jar \
- changeLogFile=liquibase/db.changeLog.xml \
--url=jdbc:mysql://127.0.0.1:3306/test \
--username=root
--password=root
generateChangeLog
複製代碼
generateChangeLog不支持存儲過程,函數以及觸發器
- 在application.properties中配置changeLog路徑:
# Liquibase配置 liquibase=true # changelog默認路徑 liquibase.change-log=classpath:/db/changelog/sqlData.xml 複製代碼
- xml配置sample:
<?xml version="1.0" encoding="UTF-8" standalone="no"?> <databaseChangeLog xmlns="http://www.liquibase.org/xml/ns/dbchangelog" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-2.0.xsd"> <changeSet author="chova" id="sql-01"> <sqlFile path="classpath:db/changelog/sqlfile/init.sql" encoding="UTF-8" /> <sqlFile path="classpath:db/changelog/sqlfile/users.sql" encoding="UTF-8" /> </changeSet> <changeSet author="chova" id="sql-02"> <sqlFile path="classpath:db/changelog/sqlfile/users2.sql" encoding="UTF-8" /> </changeSet> </databaseChangeLog> 複製代碼
- 待執行的SQL語句 - init.sql:
CREATE TABLE usersTest( user_id varchar2(14) DEFAULT '' NOT NULL, user_name varchar2(128) DEFAULT '' NOT NULL )STORAGE(FREELISTS 20 FREELIST GROUPS 2) NOLOGGING TABLESPACE USER_DATA; insert into usersTest(user_id,user_name) values ('0','test'); 複製代碼
- 啓動項目.
- 在maven配置插件生成已有數據庫的changelog文件: 須要在pom.xml中增長配置,而後配置liquibase.properties
<build> <plugins> <plugin> <groupId>org.liquibase</groupId> <artifactId>liquibase-maven-plugin</artifactId> <version>3.4.2</version> <configuration> <propertyFile>src/main/resources/liquibase.properties</propertyFile> <propertyFileWillOverride>true</propertyFileWillOverride> <!--生成文件的路徑--> <outputChangeLogFile>src/main/resources/changelog_dev.xml</outputChangeLogFile> </configuration> </plugin> </plugins> </build> 複製代碼
changeLogFile=src/main/resources/db/changelog/sqlData.xml driver=oracle.jdbc.driver.OracleDriver url=jdbc:oracle:thin:@chova username=chova password=123456 verbose=true # 生成文件的路徑 outputChangeLogFile=src/main/resources/changelog.xml 複製代碼
而後執行 [ mvn liquibase:generateChangeLog ] 命令,就是生成changelog.xml文件
- liquibase:update
- 執行changeLog中的變動
mnv liquibase:update 複製代碼
- liquibase:rollback
- rollbackCount: 表示rollback的changeSet的個數
- rollbackDate: 表示rollback到指定日期
- rollbackTag: 表示rollback到指定的tag, 須要使用liquibase在具體的時間點上打上tag
- rollbackCount示例:
mvn liquibase:rollback -Dliquibase.rollbackCount=3 複製代碼
- rollbackDate示例: 須要注意日期格式,必須匹配當前平臺執行DateFormat.getDateInstance() 獲得的格式,好比 MMM d, yyyy
mvn liquibase:rollback -Dliquibase.rollbackDate="Apr 10, 2020" 複製代碼
- rollbackTag示例: 使用tag標識,須要先打tag, 而後rollback到tag
mvn liquibase:tag -Dliquibase.tag=tag20200410 mvn liquibase:rollback -Dliquibase.rollbackTag=tag20200410 複製代碼
V1_INIT_DATABASE.sql
複製代碼
flyway.sql-migration-prefix=指定前綴
複製代碼
- 在一個空數據庫上部署集成flyway應用:
- 應用程序啓動時 ,flyway在這個數據庫中建立一張表,用於記錄migration的執行狀況,表名默認爲:schema_version:
- 而後 ,flyway根據表中的記錄決定是否執行應用程序包中提供的migration:
- 最後,將執行結果寫入schema_version中並校驗執行結果:
- 下次版本迭代時,提供新的migration, 會根據schema_version的記錄執行新的migration:
列名 | 類型 | 是否爲null | 鍵值 | 默認值 |
---|---|---|---|---|
version_rank | int(11) | 否 | MUL | NULL |
installed_rank | int(11) | 否 | MUL | NULL |
version | varchar(50) | 否 | PRI | NULL |
description | varchar(200) | 否 | NULL | |
type | varchar(20) | 否 | NULL | |
script | varchar(1000) | 否 | NULL | |
checksum | int(11) | 是 | NULL | |
installed_by | varchar(100) | 否 | NULL | |
installed_on | timestamp | 否 | CURRENT_TIMESTAMP | |
execution_time | int(11) | 否 | NULL | |
success | tinyint(1) | 否 | MUL | NULL |
##### Migration |
- 確保版本號惟一 ,flyway按照版本號順序執行 . repeatable沒有版本號,由於repeatable migration會在內容改變時重複執行
- 默認狀況下 ,flyway會將單個migration放在一個事務裏執行,也能夠經過配置將全部migration放在同一個事務裏執行
- Java API: 經過實現org.flywaydb.core.api.migration.jdbc.JdbcMigration接口來建立一個Migration, 也就是經過JDBC來執行SQL, 對於類是CLOB或者BLOB這種不適合在SQL中實現的腳本比較方便
public class V1_2_Another_user implements JdbcMigration { public void migrate(Connection connection) throws Exception { PreparedStatement statement = connection.prepareStatement("INSERT INTO test_user (name) VALUES ("Oxford")"); try { statement.execute(); } finally { statement.close(); } } } 複製代碼
- SQL腳本: 簡單的SQL腳本文件
// 單行命令 CREATE TABLE user (name VARCHAR(25) NOT NULL, PRIMARY KEY(name)); // 多行命令 -- Placeholder INSERT INTO ${tableName} (name) VALUES ("oxford"); 複製代碼
Name | Execution |
---|---|
beforeMigrate | Before Migrate runs |
beforeEachMigrate | Before every single migration during Migrate |
afterEachMigrate | After every single successful migration during Migrate |
afterEachMigrateError | After every single failed migration during Migrate |
afterMigrate | After successful Migrate runs |
afterMigrateError | After failed Migrate runs |
beforeClean | Before clean runs |
afterClean | After successful Clean runs |
afterCleanError | After failed Clean runs |
beforeInfo | Before Info runs |
afterInfo | After successful Info runs |
afterInfoError | After failed Info runs |
beforeValidate | Before Validate runs |
afterValidate | After successful Validate runs |
afterValidateError | After failed Validate runs |
beforeBaseline | Before Baseline runs |
afterBaseline | After successful Baseline runs |
afterBaselineError | After failed Baseline runs |
beforeRepair | BeforeRepair |
afterRepair | After successful Repair runs |
afterRepairError | After failed Repair runs |
# MySQL
flyway.url=jdbc:mysql://localhost:3306/db?serverTimezone=UTC&useSSL=true
# H2
flyway.url=jdbc:h2:./.tmp/db
# Hsql
flyway.url=jdbc:hsqldb:hsql//localhost:1476/db
# PostgreSQL
flyway.url=jdbc:postgresql://localhost:5432/postgres?currentSchema=schema
複製代碼
<dependency>
<groupId>org.flywaydb</groupId>
<artifactId>flyway-core</artifactId>
<version>5.0.3</version>
</dependency>
<plugin>
<groupId>org.flywaydb</groupId>
<artifactId>flyway-maven-plugin</artifactId>
<version>5.0.3</version>
</plugin>
複製代碼
server.port=8080
spring.datasource.url=jdbc:mysql://127.0.0.1:3306/db
spring.datasource.username=root
spring.datasource.password=123456
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
複製代碼
use db;
CREATE TABLE person (
id int(1) NOT NULL AUTO_INCREMENT,
firstname varchar(100) NOT NULL,
lastname varchar(100) NOT NULL,
dateofbirth DATE DEFAULT NULL,
placeofbirth varchar(100) NOT NULL, PRIMARY KEY (id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8
insert into person (firstname,lastname,dateofbirth,placeofbirth) values ('oxford','Eng',STR_TO_DATE('02/10/1997', '%m/%d/%Y'),'China');
insert into person (firstname,lastname,dateofbirth,placeofbirth) values ('oxfordd','Engg',STR_TO_DATE('02/10/1995', '%m/%d/%Y'),'China');
複製代碼
屬性名 | 默認值 | 描述 |
---|---|---|
baseline-description | / | 對執行遷移時基準版本的描述 |
baseline-on-migrate | false | 當遷移發現目標schema非空,並且帶有沒有元數據的表時,是否自動執行基準遷移 |
baseline-version | 1 | 開始執行基準遷移時對現有的schema的版本設置標籤 |
check-location | false | 檢查遷移腳本的位置是否存在 |
clean-on-validation-error | false | 校驗錯誤時是否自動調用clean操做清空數據 |
enabled | true | 是否開啓flyway |
encoding | UTF-8 | 設置遷移時的編碼 |
ignore-failed-future-migration | false | 當讀取元數據時,是否忽略錯誤的遷移 |
init-sqls | / | 初始化鏈接完成時須要執行的SQL |
locations | db/migration | 遷移腳本的位置 |
out-of-order | false | 是否容許無序遷移 |
password | / | 目標數據庫密碼 |
placeholder-prefix | ${ | 設置每一個placeholder的前綴 |
placeholder-suffix | } | 設置每一個placeholder的後綴 |
placeholders.[placeholder name] | / | 設置placeholder的value |
placeholder-replacement | true | placeholders是否要被替換 |
schemas | 默認的schema | 設置flyway須要遷移的schema,大小寫敏感 |
sql-migration-prefix | V | 遷移文件的前綴 |
sql-migration-separator | _ _ | 遷移腳本的文件名分隔符 |
sql-migration-suffix | .sql | 遷移腳本的後綴 |
tableflyway | schema_version | 使用的元數據表名 |
target | latest version | 遷移時使用的目標版本 |
url | 配置的主數據源 | 遷移時使用的JDBC URL |
user | / | 遷移數據庫的用戶名 |
validate-on-migrate | true | 遷移時是否校驗 |
# 部署-Docker | ||
### Docker基本概念 |
FROM ubuntu:18.04
COPY . /app
RUN make /app
CMD python /app/app.py
複製代碼
db:
image: "postgres:${POSTGRES_VERSION}"
複製代碼
docker inspect --format='{{.LogPath}}' $INSTANCE_ID
複製代碼
這裏有多種增長構建步驟的方式,在這裏介紹Execute shell和Invoke Ant
配置完成後,點擊[保存]
- 項目視圖:
- 安裝Role Strategy Plugin插件
- 安裝Role Stratey Plugin後進入系統設置頁面,按照以下配置後,點擊 [保存] :
- 點擊 [系統管理] -> [Manage and Assign Roles] 進入角色管理頁面:
- 選擇 [Manager Roles], 按照下圖配置後點擊 [保存]:
- job_read只加overall的read權限
- job_create只加job的create權限
- project roles中Pattern正則表達式和腳本里的是不同的:
- 好比過濾TEST開頭的jobs,要寫成 : TEST.,而不是 TEST
- 進入**[系統設置]** -> [Manage and Assign Roles] -> [Assign Roles] , 按照以下模板配置後,點擊 [保存]
- Anonymous必須變成用戶,給job_create組和job_read組權限,不然將沒有OverAll的read權限
- project roles: 用於對應用戶不一樣的權限
- 驗證: 登陸對應的用戶權限後查看用戶相關權限
- 視圖經過正則表達式過濾job: 設置正則表達式爲wechat.*,表示過濾全部以wechat開頭的項目
- 設置後的效果如圖:
import org.junit.AfterClass;
import org.junit.BeforeClass;
import org.testng.annotations.Test;
public class TestNGLearn1 {
@BeforeClass
public void beforeClass() {
System.out.println("this is before class");
}
@Test
public void TestNgLearn() {
System.out.println("this is TestNG test case");
}
@AfterClass
public void afterClass() {
System.out.println("this is after class");
}
}
複製代碼
註解 | 描述 |
---|---|
@BeforeSuit | 註解方法只運行一次,在此套件中全部測試以前運行 |
@AfterSuite | 註解方法只運行一次,在此套件中全部測試以後運行 |
@BeforeClass | 註解方法只運行一次,在當前類中全部方法調用以前運行 |
@AfterClass | 註解方法只運行一次,在當前類中全部方法調用以後運行 |
@BeforeTest | 只運行一次,在全部的測試方法執行以前運行 |
@AfterTest | 只運行一次,在全部的測試方法執行以後運行 |
@BeforeGroups | 組的列表,配置方法以前運行. 此方法是保證在運行屬於任何這些組的第一個測試,該方法將被調用 |
@AfterGroups | 組的名單,配置方法以後運行. 此方法是保證運行屬於任何這些組的最後一個測試後不久,,該方法將被調用 |
@BeforeMethod | 在每個@test測試方法運行以前運行 好比:在執行完測試用例後要重置數據才能執行第二條測試用例時,可使用這種註解方式 |
@AfterMethod | 在每個@test測試方法運行以後運行 |
@DataProvider | 標誌一個方法,提供數據的一個測試方法 註解的方法必須返回一個Object[][],其中每一個對象的[]的測試方法的參數列表能夠分配 若是有@Test方法,想要使用從這個DataProvider中接收的數據,須要使用一個dataProvider名稱等於這個註解的名稱 |
@Factory | 做爲一個工廠,返回TestNG的測試類對象中被用於標記的方法 該方法必須返回Object[] |
@Listeners | 定義一個測試類的監聽器 |
@Parameters | 定義如何將參數傳遞給@Test方法 |
@Test | 標記一個類或者方法做爲測試的一部分 |
屬性 | 描述 |
---|---|
name | 套件suite的名稱,這個名稱會出如今測試報告中 |
junit | 是否以junit模式運行 |
verbose | 設置在控制檯中的輸出方式. 這個設置不影響html版本的測試報告 |
parallel | 是否使用多線程進行測試,能夠加速測試 |
configfailurepolicy | 是否在運行失敗了一次以後繼續嘗試或者跳過 |
thread-count | 若是設置了parallel,能夠設置線程數 |
annotations | 若是有javadoc就在javadoc中尋找,沒有就使用jdk5的註釋 |
time-out | 在終止method(parallel="methods")或者test(parallel="tests")以前設置以毫秒爲單位的等待時間 |
skipfailedinvocationcounts | 是否跳過失敗的調用 |
data-provider-thread-count | 提供一個線程池的範圍來使用parallel data |
object-factory | 用來實例化測試對象的類,繼承自IObjectFactory類 |
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE suite SYSTEM "http://testng.org/testng-1.0.dtd" >
<suite name="Suite" parallel="tests" thread-count="5">
<test name="Test" preserve-order="true" verbose="2">
<parameter name="userName" value="15952031403"></parameter>
<parameter name="originPwd" value="c12345"></parameter>
<classes>
<class name="com.oxford.testng.RegisterTest">
</class>
</classes>
</test>
<test name="Test1" preserve-order="true">
<classes>
<class name="com.oxford.testng.Test2">
</class>
</classes>
</test>
<test name="Test2" preserve-order="true">
<classes>
<class name="com.oxford.testng.Test3">
</class>
</classes>
</test>
</suite>
複製代碼
<?xml version="1.0" encoding="UTF-8"?>
<data>
<login>
<username>user1</username>
<password>123456</password>
</login>
<login>
<username>user2</username>
<password>345678</password>
</login>
</data>
複製代碼
import java.io.File;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;
public class ParseXml {
/** * 利用Dom4j解析xml文件,返回list * @param xmlFileName * @return */
public static List parse3Xml(String xmlFileName){
File inputXml = new File(xmlFileName);
List list= new ArrayList();
int count = 1;
SAXReader saxReader = new SAXReader();
try {
Document document = saxReader.read(inputXml);
Element items = document.getRootElement();
for (Iterator i = items.elementIterator(); i.hasNext();) {
Element item = (Element) i.next();
Map map = new HashMap();
Map tempMap = new HashMap();
for (Iterator j = item.elementIterator(); j.hasNext();) {
Element node = (Element) j.next();
tempMap.put(node.getName(), node.getText());
}
map.put(item.getName(), tempMap);
list.add(map);
}
} catch (DocumentException e) {
System.out.println(e.getMessage());
}
System.out.println(list.size());
return list;
}
}
複製代碼
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import org.testng.Assert;
import org.testng.annotations.DataProvider;
public class GenerateData {
public static List list = new ArrayList();
@DataProvider(name = "dataProvider")
public static Object[][] dataProvider(Method method){
list = ParseXml.parse3Xml("absolute path of xml file");
List<Map<String, String>> result = new ArrayList<Map<String, String>>();
for (int i = 0; i < list.size(); i++) {
Map m = (Map) list.get(i);
if(m.containsKey(method.getName())){
Map<String, String> dm = (Map<String, String>) m.get(method.getName());
result.add(dm);
}
}
if(result.size() > 0){
Object[][] files = new Object[result.size()][];
for(int i=0; i<result.size(); i++){
files[i] = new Object[]{result.get(i)};
}
return files;
}else {
Assert.assertTrue(result.size()!=0,list+" is null, can not find"+method.getName() );
return null;
}
}
}
複製代碼
public class LoginTest {
@Test(dataProvider="dataProvider", dataProviderClass= GenerateData.class)
public void login(Map<String, String> param) throws InterruptedException{
List<WebElement> edits = findElementsByClassName(AndroidClassName.EDITTEXT);
edits.get(0).sendkeys(param.get("username"));
edits.get(1).sendkeys(param.get("password"));
}
}
複製代碼
package com.oxford.listener;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import org.testng.ITestContext;
import org.testng.ITestResult;
import org.testng.TestListenerAdapter;
import com.google.gson.Gson;
import com.google.gson.JsonObject;
import com.unionpay.base.BaseTest;
import com.unionpay.constants.CapabilitiesBean;
import com.unionpay.constants.CaseCountBean;
import com.unionpay.constants.ResultBean;
import com.unionpay.util.Assertion;
import com.unionpay.util.PostService;
import com.unionpay.util.ReadCapabilitiesUtil;
/** * 帶有post請求的testng監聽 * @author lichen2 */
public class TestNGListenerWithPost extends TestListenerAdapter{
//接收每一個case結果的接口
private String caseUrl;
//接收整個test運行數據的接口
private String countUrl;
//接收test運行狀態的接口
private String statusUrl;
private JsonObject caseResultJson = new JsonObject();
private JsonObject caseCountJson = new JsonObject();
private Gson gson = new Gson();
private ResultBean result = new ResultBean();
private CaseCountBean caseCount = new CaseCountBean();
private SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
private CapabilitiesBean capabilitiesBean = ReadCapabilitiesUtil.readCapabilities("setting.json");
private String testStartTime;
private String testEndTime;
private String runId;
//testng初始化
@Override
public void onStart(ITestContext testContext) {
super.onStart(testContext);
String serverUrl = capabilitiesBean.getServerurl();
caseUrl = "http://"+serverUrl+"/api/testcaseResult";
countUrl = "http://"+serverUrl+"/api/testcaseCount";
statusUrl = "http://"+serverUrl+"/api/testStatus";
runId = capabilitiesBean.getRunid();
result.setRunId(runId);
caseCount.setRunId(runId);
}
//case開始
@Override
public void onTestStart(ITestResult tr) {
Assertion.flag = true;
Assertion.errors.clear();
sendStatus("運行中");
result.setStartTime(format.format(new Date()));
}
//case成功執行
@Override
public void onTestSuccess(ITestResult tr) {
super.onTestSuccess(tr);
sendResult(tr);
takeScreenShot(tr);
}
//case執行失敗
@Override
public void onTestFailure(ITestResult tr) {
super.onTestFailure(tr);
sendResult(tr);
try {
takeScreenShot(tr);
} catch (SecurityException e) {
e.printStackTrace();
} catch (IllegalArgumentException e) {
e.printStackTrace();
}
this.handleAssertion(tr);
}
//case被跳過
@Override
public void onTestSkipped(ITestResult tr) {
super.onTestSkipped(tr);
takeScreenShot(tr);
sendResult(tr);
this.handleAssertion(tr);
}
//全部case執行完成
@Override
public void onFinish(ITestContext testContext) {
super.onFinish(testContext);
sendStatus("正在生成報告");
sendFinishData(testContext);
}
/** * 發送case測試結果 * @param tr */
public void sendResult(ITestResult tr){
result.setTestcaseName(tr.getName());
result.setEndTime(format.format(new Date()));
float tmpDuration = (float)(tr.getEndMillis() - tr.getStartMillis());
result.setDuration(tmpDuration / 1000);
switch (tr.getStatus()) {
case 1:
result.setTestResult("SUCCESS");
break;
case 2:
result.setTestResult("FAILURE");
break;
case 3:
result.setTestResult("SKIP");
break;
case 4:
result.setTestResult("SUCCESS_PERCENTAGE_FAILURE");
break;
case 16:
result.setTestResult("STARTED");
break;
default:
break;
}
caseResultJson.addProperty("result", gson.toJson(result));
PostService.sendPost(caseUrl, caseResultJson.toString());
}
/** * 通知test完成 * @param testContext */
public void sendFinishData(ITestContext tc){
testStartTime = format.format(tc.getStartDate());
testEndTime = format.format(tc.getEndDate());
long duration = getDurationByDate(tc.getStartDate(), tc.getEndDate());
caseCount.setTestStartTime(testStartTime);
caseCount.setTestEndTime(testEndTime);
caseCount.setTestDuration(duration);
caseCount.setTestSuccess(tc.getPassedTests().size());
caseCount.setTestFail(tc.getFailedTests().size());
caseCount.setTestSkip(tc.getSkippedTests().size());
caseCountJson.addProperty("count", gson.toJson(caseCount));
PostService.sendPost(countUrl, caseCountJson.toString());
}
/** * 通知test運行狀態 */
public void sendStatus(String status){
JsonObject jsonObject = new JsonObject();
jsonObject.addProperty("runId", runId);
jsonObject.addProperty("status", status);
JsonObject sendJson = new JsonObject();
sendJson.addProperty("status", jsonObject.toString());
PostService.sendPost(statusUrl, sendJson.toString());
}
//計算date間的時差(s)
public long getDurationByDate(Date start, Date end){
long duration = end.getTime() - start.getTime();
return duration / 1000;
}
//截圖
private void takeScreenShot(ITestResult tr) {
BaseTest b = (BaseTest) tr.getInstance();
b.takeScreenShot(tr);
}
}
複製代碼
package com.oxford.base;
import org.testng.ITestResult;
import com.unionpay.listener.TestNGListenerWithPost;
@Listeners(TestNGListenerWithPost.class)
public abstract class BaseTest {
public AndroidDriver<WebElement> driver;
public BaseTest() {
driver = DriverFactory.getDriverByJson();
}
/** * 截屏並保存到本地 * @param tr */
public void takeScreenShot(ITestResult tr) {
String fileName = tr.getName() + ".jpg";
File dir = new File("target/snapshot");
if (!dir.exists()) {
dir.mkdirs();
}
String filePath = dir.getAbsolutePath() + "/" + fileName;
if (driver != null) {
try {
File scrFile = driver.getScreenshotAs(OutputType.FILE);
FileUtils.copyFile(scrFile, new File(filePath));
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
複製代碼