共计 77930 个字符,预计需要花费 195 分钟才能阅读完成。
次要技术
- 根底框架: springboot
- 微服务架构: dubbo,springboot cloud
- ORM 框架: mybatis plus
- 数据库连接池: Alibaba Druid
- 网关(对立对外接口): zuul
- 缓存: redis
- 注册核心: zookeeper,eureka
- 音讯队列:
- 作业调度框架: Quartz
- 分布式文件系统:
- 接口测试框架: Swagger2
- 数据库版本控制: Liquibase (flyway)
- 部署: docker
- 继续集成: jenkins
-
自动化测试: testNG
ORM 框架 -Mybatis Plus
MyBatis Plus 是在 MyBatis 的根底上只做加强不做扭转, 能够简化开发, 提高效率.
Mybatis Plus 外围性能
- 反对通用的 CRUD, 代码生成器与条件结构器
- 通用 CRUD: 定义好 Mapper 接口后, 只须要继承 BaseMapper<T> 接口即可取得通用的增删改查性能, 无需编写任何接口办法与配置文件
- 条件结构器: 通过 EntityWrapper<T>(实体包装类), 能够用于拼接 SQL 语句, 并且反对排序, 分组查问等简单的 SQL
-
代码生成器: 反对一系列的策略配置与全局配置, 比 MyBatis 的代码生成更好用
BaseMapper<T> 接口中通用的 CRUD 办法:MyBatis Plus 与 SpringBoot 集成
-
数据库 USER
DROP TABLE IF EXISTS user; CREATE TABLE user(id bigint(20) DEFAULT NULL COMMENT '惟一标示', code varchar(20) DEFAULT NULL COMMENT '编码', name varchar(64) DEFAULT NULL COMMENT '名称', status char(1) DEFAULT 1 COMMENT '状态 1 启用 0 停用', gmt_create datetime DEFAULT CURRENT_TIMESTAMP COMMENT '创立工夫', gmt_modified datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '批改工夫' ) ENGINE=InnoDB DEFAULT CHARSET=utf8;
-
pom.xml 依赖
<!--mybatis plus --> <dependency> <groupId>com.baomidou</groupId> <artifactId>mybatisplus-spring-boot-starter</artifactId> <version>1.0.5</version> </dependency> <dependency> <groupId>com.baomidou</groupId> <artifactId>mybatis-plus</artifactId> <version>2.1.9</version> </dependency>
-
spring-mybatis.xml 配置文件
也能够间接应用 @Bean 的形式进行或者通过 application 配置文件进行<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:tx="http://www.springframework.org/schema/tx" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd"> <!-- 创立 jdbc 数据源 这里间接应用阿里的 druid 数据库连接池 --> <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource" destroy-method="close"> <property name="driverClassName" value="${mysql.driver}"/> <property name="url" value="${mysql.url}"/> <property name="username" value="${mysql.username}"/> <property name="password" value="${mysql.password}"/> <!-- 初始化连贯大小 --> <property name="initialSize" value="0"/> <!-- 连接池最大应用连贯数量 --> <property name="maxActive" value="20"/> <!-- 连接池最大闲暇 --> <property name="maxIdle" value="20"/> <!-- 连接池最小闲暇 --> <property name="minIdle" value="0"/> <!-- 获取连贯最大等待时间 --> <property name="maxWait" value="60000"/> <property name="validationQuery" value="${validationQuery}"/> <property name="testOnBorrow" value="false"/> <property name="testOnReturn" value="false"/> <property name="testWhileIdle" value="true"/> <!-- 配置距离多久才进行一次检测,检测须要敞开的闲暇连贯,单位是毫秒 --> <property name="timeBetweenEvictionRunsMillis" value="60000"/> <!-- 配置一个连贯在池中最小生存的工夫,单位是毫秒 --> <property name="minEvictableIdleTimeMillis" value="25200000"/> <!-- 关上 removeAbandoned 性能 --> <property name="removeAbandoned" value="true"/> <!-- 1800 秒,也就是 30 分钟 --> <property name="removeAbandonedTimeout" value="1800"/> <!-- 敞开 abanded 连贯时输入谬误日志 --> <property name="logAbandoned" value="true"/> <!-- 监控数据库 --> <property name="filters" value="mergeStat"/> </bean> <!-- (事务管理)transaction manager, use JtaTransactionManager for global tx --> <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <property name="dataSource" ref="dataSource" /> </bean> <!-- 可通过注解管制事务 --> <tx:annotation-driven transaction-manager="transactionManager"/> <!--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.springboot.chapter9.biz.entity"/> <!-- MP 全局配置注入 --> <property name="globalConfig" ref="globalConfig"/> </bean> <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="3" /> </bean> <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer"> <!-- 主动扫描包门路,接口主动注册为一个 bean 类 --> <property name="basePackage" value="cn.lqdev.learning.springboot.chapter9.biz.dao"/> </bean> </beans>
-
编写启动类,利用启动时主动加载配置 xml 文件
@Configuration @ImportResource(locations = {"classpath:/mybatis/spring-mybatis.xml"}) //@MapperScan("cn.lqdev.learning.springboot.chapter9.biz.dao") //@EnableTransactionManagement public class MybatisPlusConfig {}
MyBatis Plus 集成 Spring
-
数据表构造
DROP TABLE IF EXISTS tbl_employee; CREATE TABLE tbl_employee(id int(11) NOT NULL AUTO_INCREMENT, last_name varchar(50) DEFAULT NULL, email varchar(50) DEFAULT NULL, gender char(1) DEFAULT NULL, age int(11) DEFAULT NULL, PRIMARY KEY (id) ) ENGINE=InnoDB AUTO_INCREMENT=8 DEFAULT CHARSET=utf8;
-
pom.xml
<dependencies> <!-- MP --> <dependency> <groupId>com.baomidou</groupId> <artifactId>mybatis-plus</artifactId> <version>2.3</version> </dependency> <!-- 测试 --> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.12</version> </dependency> <!-- 数据源 --> <dependency> <groupId>com.alibaba</groupId> <artifactId>druid</artifactId> <version>1.1.10</version> </dependency> <!-- 数据库驱动 --> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>5.1.39</version> </dependency> <!-- Spring 相干 --> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> <version>4.3.9.RELEASE</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-orm</artifactId> <version>4.3.9.RELEASE</version> </dependency> </dependencies>
-
MyBatis 全局配置文件 mybatis-config.xml
<?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 />
-
数据源 db.properties
jdbc.url=jdbc:mysql://localhost:3306/mp jdbc.username=mp jdbc.password=mp
-
Spring 配置文件 applicationContext.xml
<!-- 数据源 --> <context:property-placeholder location="classpath:db.properties"/> <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource"> <property name="url" value="${jdbc.url}"></property> <property name="username" value="${jdbc.username}"></property> <property name="password" value="${jdbc.password}"></property> </bean> <!-- MP 提供的 MybatisSqlSessionFactoryBean --> <bean id="sqlSessionFactoryBean" class="com.baomidou.mybatisplus.spring.MybatisSqlSessionFactoryBean"> <!-- 数据源 --> <property name="dataSource" ref="dataSource"></property> <!-- mybatis 全局配置文件 --> <property name="configLocation" value="classpath:mybatis-config.xml"></property> <!-- 别名解决 --> <property name="typeAliasesPackage" value="com.jas.bean"></property> <!-- 注入全局 MP 策略配置 --> <property name="globalConfig" ref="globalConfiguration"></property> <!-- 插件注册 --> <property name="plugins"> <list> <!-- 注册分页插件 --> <bean class="com.baomidou.mybatisplus.plugins.PaginationInterceptor" /> <!-- 注入 SQL 性能剖析插件,倡议在开发环境中应用,能够在控制台查看 SQL 执行日志 --> <bean class="com.baomidou.mybatisplus.plugins.PerformanceInterceptor"> <property name="maxTime" value="1000" /> <!--SQL 是否格式化 默认 false--> <property name="format" value="true" /> </bean> </list> </property> </bean> <!-- 定义 MybatisPlus 的全局策略配置 --> <bean id ="globalConfiguration" class="com.baomidou.mybatisplus.entity.GlobalConfiguration"> <!-- 在 2.3 版本当前,dbColumnUnderline 默认值是 true --> <property name="dbColumnUnderline" value="true"></property> <!-- 全局的主键策略 --> <property name="idType" value="0"></property> <!-- 全局的表前缀策略配置 --> <property name="tablePrefix" value="tbl_"></property> </bean> <!-- 配置 mybatis 扫描 mapper 接口的门路 --> <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer"> <property name="basePackage" value="com.jas.mapper"></property> </bean>
MyBatis Plus 应用示例
-
实体类 Employee
@TableName(value = "tbl_employee") public class Employee {@TableId(value = "id", type = IdType.AUTO) private Integer id; @TableField(value = "last_name") private String lastName; private String email; private Integer gender; private Integer age; public Employee() {super(); } public Employee(Integer id, String lastName, String email, Integer gender, Integer age) { this.id = id; this.lastName = lastName; this.email = email; this.gender = gender; this.age = age; } // 省略 set、get 与 toString() 办法
-
mapper 接口
-
不定义任何接口办法
*/
public interface EmployeeMapper extends BaseMapper<Employee> {} -
在测试类中生成测试的 mapper 对象
private ApplicationContext context = new ClassPathXmlApplicationContext("classpath:applicationContext.xml"); private EmployeeMapper employeeMapper = context.getBean("employeeMapper", EmployeeMapper.class);
-
查问:
@Test public void getEmpByIdTest() {Employee employee = employeeMapper.selectById(1); System.out.println(employee); }
-
分页查问:
@Test public void getEmpByPage() {Page<?> page = new Page<>(1, 5); List<Employee> list = employeeMapper.selectPage(page, null); System.out.println("总记录数:" + page.getTotal()); System.out.println("总页数" + page.getPages()); System.out.println(list); }
-
条件结构器:
@Test public void getEmpByName() {EntityWrapper<Employee> wrapper = new EntityWrapper<>(); // 'last_name' 与 'age' 对应数据库中的字段 wrapper.like("last_name", "张"); wrapper.eq("age", 20); List<Employee> list = employeeMapper.selectList(wrapper); System.out.println(list); }
控制台输入的 SQL 剖析日志
简略的数据库操作不须要在 EmployeeMapper 接口中定义任何办法, 也没有在配置文件中编写 SQL 语句, 而是通过继承 BaseMapper<T> 接口取得通用的的增删改查办法, 简单的 SQL 也能够应用条件结构器拼接. 不过简单的业务需要还是要编写 SQL 语句的, 流程和 MyBatis 一样.MyBatis Plus 应用场景
代码生成器
-
代码生成器依赖 velocity 模版引擎, 引入依赖
<dependency> <groupId>org.apache.velocity</groupId> <artifactId>velocity-engine-core</artifactId> <version>2.0</version> <scope>test</scope> </dependency>
-
代码生成器类 MysqlGenerator:
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();} }
通用 CRUD
-
通用 CRUD 测试类 GeneralTest:
@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()); } }
-
MyBatis Plus 定义的数据库操作方法
对于通用代码如何注入的, 可查看 com.baomidou.mybatisplus.mapper.AutoSqlInjector 类, 这个就是注入通用的 CURD 办法的类.条件结构器
条件结构器次要提供了实体包装器, 用于解决 SQL 语句拼接, 排序, 实体参数查问:应用的是数据库字段,不是 Java 属性
-
sql 条件拼接:
SQL 条件拼接测试类 ConditionTest@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<T>
-
自定义 SQL 应用条件结构器:
UserDao.java 退出接口办法:/** * * @param rowBounds 分页对象 间接传入 page 即可 * @param wrapper 条件结构器 * @return */ List<User> selectUserWrapper(RowBounds rowBounds, @Param("ew") Wrapper<User> wrapper);
UserMapper.xml 退出对应的 xml 节点:
<!-- 条件结构器模式 --> <select id="selectUserWrapper" resultType="user"> SELECT <include refid="Base_Column_List" /> FROM USER <where> ${ew.sqlSegment} </where> </select>
自定义 SQL 应用条件结构器测试类:
@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 完结"); }
-
xml 模式应用 wrapper:
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);
-
xml 模式:
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 的插件机制应用只须要注册即可
-
mybatis-config.xml
<plugins> <!-- SQL 执行性能剖析,开发环境应用,线上不举荐。--> <plugin interceptor="com.baomidou.mybatisplus.plugins.PerformanceInterceptor"></plugin> <!-- 分页插件配置 --> <plugin interceptor="com.baomidou.mybatisplus.plugins.PaginationInterceptor"></plugin> </plugins>
-
分页测试类(性能剖析, 配置后能够输入 sql 及取数时间):
@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 中就提供了这么一个公共字段主动填充性能
- 设置填充字段的填充类型:
-
User
== 留神 == 能够在代码生成器外面配置规定的, 可主动配置/** * 创立工夫 */ @TableField(fill=FieldFill.INSERT) private Date gmtCreate; /** * 批改工夫 */ @TableField(fill=FieldFill.INSERT_UPDATE) private Date gmtModified;
- 定义解决类:
-
MybatisObjectHandler
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); } }
-
批改 springb-mybatis.xml 文件, 退出此配置
<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')
数据库连接池 -Alibaba Druid
-
Druid 是 JDBC 组件, 包含三个局部:
- DruidDriver: 代理 Driver, 可能提供基于 Filter-Chain 模式的插件体系
- DruidDataSource: 高效可治理的数据库连接池
- SQL Parser: Druid 内置应用 SQL Parser 来实现进攻 SQL 注入(WallFilter), 合并统计没有参数化的 SQL(StatFilter 的 mergeSql),SQL 格式化, 分库分表
-
Druid 的作用:
- 监控数据库拜访性能: Druid 内置提供了一个功能强大的 StatFilter 插件, 可能具体统计 SQL 的执行性能, 晋升线上剖析数据库拜访性能
- 替换 DBCP 和 C3P0: Druid 提供了一个高效, 功能强大, 可扩展性好的数据库连接池
- 数据库明码加密: 间接把数据库明码写在配置文件容易导致平安问题,DruidDruiver 和 DruidDataSource 都反对 PasswordCallback
- 监控 SQL 执行日志: Druid 提供了不同的 LogFilter, 可能反对 Common-Logging,Log4j 和 JdkLog, 能够按须要抉择相应的 LogFilter, 监控数据库拜访状况
- 扩大 JDBC: 通过 Druid 提供的 Filter-Chain 机制, 编写 JDBC 层的扩大
- 配置参数: Druid 的 DataSource:com.alibaba.druid.pool.DruidDataSource
配置参数 | 缺省值 | 阐明 |
---|---|---|
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 数据结构
- Druid 架构相辅相成的是基于 DataSource 和 Segment 的数据结构
-
DataSource 数据结构: 是 逻辑概念, 与传统的关系型数据库相比拟 DataSource 能够了解为表
- 工夫列: 表明每行数据的工夫值
- 维度列: 表明数据的各个维度信息
- 指标列: 须要聚合的列的数据
-
Segment 构造: 理论的物理存储格局,
- Druid 通过 Segment 实现了横纵向切割操作
- Druid 将不同的工夫范畴内的数据寄存在不同的 Segment 文件块中, 通过 工夫 实现了横向切割
- Segment 也 面向列进行数据压缩存储, 实现纵向切割
-
Druid 架构蕴含四个节点和一个服务:
- 实时节点(RealTime Node): 即时摄入实时数据,并且生成 Segment 文件
- 历史节点(Historical Node): 加载曾经生成好的数据文件, 以供数据查问应用
- 查问节点(Broker Node): 对外提供数据查问服务, 并且从实时节点和历史节点汇总数据, 合并后返回
- 协调节点(Coordinator Node): 负责历史节点的数据的负载平衡, 以及通过规定治理数据的生命周期
-
索引服务(Indexing Service): 有不同的获取数据的形式, 更加灵便的生成 segment 文件治理资源
实时节点
- 次要负责即时摄入实时数据, 以及生成 Segment 文件
-
实时节点通过 firehose 进行数据的摄入,firehose 是 Druid 实时生产模型
通过 kafka 生产, 就是 kafkaFireHose. 同时, 实时节点的另外一个模块 Plumer, 用于 Segment 的生成, 并且依照指定的周期, 将本周期内生成的所有数据块合并成一个
-
Segment 文件从制作到流传过程:
1. 实时节点生产出 Segment 文件, 并且存到文件系统中 2.Segment 文件的 <MetaStore> 寄存到 Mysql 等其余内部数据库中 3.Master 通过 Mysql 中的 MetaStore, 通过肯定的规定, 将 Segment 调配给属于它的节点 4. 历史节点失去 Master 发送的指令后会从文件系统中拉取属于本人的 Segment 文件, 并且通过 zookeeper, 告知集群, 本人提供了此块 Segment 的查问服务 5. 实时节点抛弃 Segment 文件, 并且申明不在提供此块文件的查问服务
历史节点
-
历史节点再启动的时候:
- 优先查看本人的本地缓存中是否曾经有了缓存的 Segment 文件
- 而后从文件系统中下载属于本人,但还不存在的 Segment 文件
- 无论是何种查问, 历史节点首先将相干的 Segment 从磁盘加载到内存. 而后再提供服务
-
历史节点的查问效率受内存空间充裕水平的影响很大:
- 内存空间充裕, 查问时须要从磁盘加载数据的次数缩小, 查问速度就快
- 内存空间有余, 查问时须要从磁盘加载数据的次数就多,查问速度就绝对较慢
-
原则上历史节点的查问速度与其内存大小和所负责的 Segment 数据文件大小成正比关系
查问节点
-
查问节点便是整个集群的查问中枢:
- 在惯例状况下,Druid 集群间接对外提供查问的节点只有查问节点, 而查问节点会将从实时节点与历史节点查问到的数据合并后返回给客户端
- Druid 应用了 Cache 机制 来进步本人的查问效率.
-
Druid 提供两类介质作为 Cache:
- 内部 cache:Memcached
-
外部 Cache: 查问节点或历史节点的内存, 如果用查问节点的内存作为 Cache, 查问的时候会首先拜访其 Cache, 只有当不命中的时候才会去拜访历史节点和实时节点查问数据
协调节点
- 对于整个 Druid 集群来说, 其实并没有真正意义上的 Master 节点.
- 实时节点与查问节点能自行治理并不听命于任何其余节点,
- 对于历史节点来说, 协调节点便是他们的 Master, 因为协调节点将会给历史节点调配数据,实现数据分布在历史节点之间的负载平衡.
- 历史节点之间是互相不进行通信的, 全副通过协调节点进行通信
-
利用规定治理数据的生命周期:
- Druid 利用针对每个 DataSoure 设置的规定来加载或者抛弃具体的文件数据, 来治理数据的生命周期
- 能够对一个 DataSource 按程序增加多条规定, 对于一个 Segment 文件来说, 协调节点会逐条查看规定
-
当碰到以后 Segment 文件负责某条规定的状况下, 协调节点会立刻命令历史节点对该文件执行此规定, 加载或者抛弃, 并进行余下的规定, 否则持续查看
索引服务
除了通过实时节点生产 Segment 文件之外,druid 还提供了一组索引服务来摄入数据
-
索引服务的长处:
- 有不同的获取数据的形式, 反对 pull 和 push
- 能够通过 API 编程的形式来配置工作
- 能够更加灵便地应用资源
- 灵便地操作 Segment 文件
-
索引服务的主从架构:
索引服务蕴含一组组件, 并以主从构造作为架构形式, 统治节点 Overload node 为主节点, 两头管理者 Middle Manager 为从节点-
Overload node: 索引服务的主节点. 对外负责接管工作申请, 对内负责将工作合成并下发到从节点即 Middle Manager. 有两种运行模式:
- 本地模式 (默认): 此模式主节点不仅须要负责集群的调度, 协调调配工作, 还须要负责启动 Peon(苦工) 来实现一部分具体的工作
-
近程模式: 主从节点别离运行在不同的节点上, 主节点只负责协调调配工作. 不负责实现工作, 并且提供 rest 服务, 因而客户端能够通过 HTTP POST 来提交工作
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
-
-
Zuul是 netflix 开源的一个 API Gateway 服务器, 实质上是一个 web servlet 利用
–Zuul 是一个基于 JVM 路由和服务端的负载均衡器, 提供动静路由,监控,弹性,平安等边缘服务的框架, 相当于是设施和 Netflix 流利用的 Web 网站后端所有申请的前门Zuul 工作原理
-
过滤器机制
-
Zuul 提供了一个框架,能够对过滤器进行动静的加载,编译,运行
1.Zuul 的过滤器之间没有间接的互相通信,他们之间通过一个 RequestContext 的动态类来进行数据传递的。RequestContext 类中有 ThreadLocal 变量来记录每个 Request 所须要传递的数据 2.Zuul 的过滤器是由 Groovy 写成,这些过滤器文件被放在 Zuul Server 上的特定目录上面,Zuul 会定期轮询这些目录,批改过的过滤器会动静的加载到 Zuul Server 中以便过滤申请应用
-
规范过滤器类型:
Zuul 大部分性能都是通过过滤器来实现的。Zuul 中定义了四种规范过滤器类型,这些过滤器类型对应于申请的典型生命周期- PRE: 在申请被路由之前调用, 利用这种过滤器实现身份验证、在集群中抉择申请的微服务、记录调试信息等
- ROUTING: 申请路由到微服务, 用于构建发送给微服务的申请, 应用 Apache HttpClient 或 Netfilx Ribbon 申请微服务
- POST: 在路由到微服务当前执行, 用来为响应增加规范的 HTTP Header、收集统计信息和指标、将响应从微服务发送给客户端等
-
ERROR: 在其余阶段产生谬误时执行该过滤器
- 内置的非凡过滤器:
- StaticResponseFilter: StaticResponseFilter 容许从 Zuul 自身生成响应,而不是将申请转发到源
- SurgicalDebugFilter: SurgicalDebugFilter 容许将特定申请路由到分隔的调试集群或主机
- 自定义的过滤器:
除了默认的过滤器类型,Zuul 还容许咱们创立自定义的过滤器类型。如 STATIC 类型的过滤器,间接在 Zuul 中生成响应,而不将申请转发到后端的微服务
-
- 过滤器的生命周期
Zuul 申请的生命周期详细描述了各种类型的过滤器的执行程序 - 过滤器调度过程
-
动静加载过滤器
Zuul 的作用
Zuul 能够通过加载动静过滤机制实现 Zuul 的性能:
- 验证与平安保障: 辨认面向各类资源的验证要求并回绝那些与要求不符的申请
- 审查与监控: 在边缘地位追踪有意义数据及统计后果,失去精确的生产状态论断
- 动静路由: 以动静形式依据须要将申请路由至不同后端集群处
- 压力测试: 逐步减少指向集群的负载流量,从而计算性能程度
- 负载调配: 为每一种负载类型调配对应容量,并弃用超出限定值的申请
- 动态响应解决: 在边缘地位间接建设局部响应,从而防止其流入外部集群
-
多区域弹性: 逾越 AWS 区域进行申请路由,旨在实现 ELB 应用多样化并保障边缘地位与使用者尽可能靠近
Zuul 与利用的集成形式
-
ZuulServlet – 解决申请(调度不同阶段的 filters,解决异样等)
- 所有的 Request 都要通过 ZuulServlet 的解决,
- Zuul 对 request 解决逻辑的三个外围的办法: preRoute(),route(), postRoute()
- ZuulServletZuulServlet 交给 ZuulRunner 去执行。因为 ZuulServlet 是单例,因而 ZuulRunner 也仅有一个实例。ZuulRunner 间接将执行逻辑交由 FilterProcessor 解决,FilterProcessor 也是单例,其性能就是根据 filterType 执行 filter 的解决逻辑
-
FilterProcessor 对 filter 的解决逻辑:
1. 首先依据 Type 获取所有输出该 Type 的 filter:List<ZuulFilter> list 2. 遍历该 list,执行每个 filter 的解决逻辑:processZuulFilter(ZuulFilter filter) 3.RequestContext 对每个 filter 的执行情况进行记录,应该注意,此处的执行状态次要包含其执行工夫、以及执行胜利或者失败,如果执行失败则对异样封装后抛出 4. 到目前为止,Zuul 框架对每个 filter 的执行后果都没有太多的解决,它没有把上一 filter 的执行后果交由下一个将要执行的 filter,仅仅是记录执行状态,如果执行失败抛出异样并终止执行
-
ContextLifeCycleFilter – RequestContext 的生命周期治理:
- ContextLifecycleFilter 的外围性能是为了革除 RequestContext; 申请上下文 RequestContext 通过 ThreadLocal 存储,须要在申请实现后删除该对象 RequestContext 提供了执行 filter Pipeline 所须要的 Context,因为 Servlet 是单例多线程,这就要求 RequestContext 即要线程平安又要 Request 平安。context 应用 ThreadLocal 保留,这样每个 worker 线程都有一个与其绑定的 RequestContext,因为 worker 仅能同时解决一个 Request,这就保障了 Request Context 即是线程平安的由是 Request 平安的。
-
GuiceFilter – GOOLE-IOC(Guice 是 Google 开发的一个轻量级, 基于 Java5(次要使用泛型与正文个性)的依赖注入框架(IOC).Guice 十分小而且快.)
- StartServer – 初始化 zuul 各个组件(ioc, 插件,filters, 数据库等)
- FilterScriptManagerServlet – uploading/downloading/managing scripts,实现热部署
Filter 源码文件放在 zuul 服务特定的目录,zuul server 会定期扫描目录下的文件的变动,动静的读取 \ 编译 \ 运行这些 filter, 如果有 Filter 文件更新,源文件会被动静的读取,编译加载进入服务,接下来的 Request 解决就由这些新退出的 filter 解决
缓存 -Redis
- Redis: Redis 是一个开源的内存中的数据结构存储系统, 能够用作 数据库 , 缓存 和消息中间件
-
操作工具:Redis Desktop Manager
整合 Redis 缓存
-
在 pom.xml 中引入 redis 依赖
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId> </dependency>
-
配置 redis, 在 application.properties 中配置 redis
spring.redis.host=192.168.32.242
-
RedisTemplate:(操作 k - v 都是对象)
@Bean @ConditionalOnMissingBean(name = {"redisTemplate"} ) public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) throws UnknownHostException {RedisTemplate<Object, Object> template = new RedisTemplate(); template.setConnectionFactory(redisConnectionFactory); return template; }
- 保留对象时,应用 JDK 的序列化机制 , 将 序列化后的数据保留到 redis中
-
为了加强 Redis 数据库中的数据可读性:
-
将对象数据以 ==json== 形式保留:
- 将对象转化为 json
-
配置 redisTemplate 的 json 序列化规定
@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(有序汇合)
-
-
StringRedisTemplate(操作 k - v 都是字符串)
在 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(有序汇合)
注册核心 -Zookeeper,Eureka
Zookeeper 基本概念
- Zookeeper 是一个分布式的, 开放源码的 分布式应用程序协调服务
- Zookeeper 是 hadoop 的一个子项目
- 蕴含一个简略的 原语集, 分布式应用程序能够基于它 实现同步服务, 配置保护和命名服务等
- 在分布式应用中, 因为工程师不能很好地应用锁机制, 以及基于音讯的协调机制不适宜在某些利用中应用,Zookeeper 提供一种 牢靠的, 可扩大的, 分布式的, 可配置的协调机制 来对立零碎的状态
- Zookeeper 中的角色:
- 零碎模型图:
-
Zookeeper 特点:
- 最终一致性: client 不管连贯到哪个 Server, 展现给它都是同一个视图, 这是 Zookeeper 最重要的性能
- 可靠性: 具备简略, 强壮, 良好的性能, 如果音讯 m 被到一台服务器承受, 那么它将被所有的服务器承受
- 实时性: Zookeeper 保障客户端将在一个工夫距离范畴内取得服务器的更新信息, 或者服务器生效的信息. 但因为网络延时等起因,Zookeeper 不能保障两个客户端能同时失去刚更新的数据, 如果须要最新数据, 应该在读数据之前调用 sync()接口
- 期待无关(wait-free): 慢的或者生效的 client 不得干涉疾速的 client 的申请, 使得每个 client 都能无效的期待
- 原子性: 更新只能胜利或者失败, 没有中间状态
-
程序性: 包含 全局有序 和偏序 两种: 全局有序是指如果在一台服务器上音讯 a 在音讯 b 前公布, 则在所有 Server 上音讯 a 都将在音讯 b 前被公布. 偏序是指如果一个音讯 b 在音讯 a 后被同一个发送者公布,a 必将排在 b 后面
Zookeeper 工作原理
- Zookeeper 的外围是原子播送, 这个机制保障了各个 Server 之间的同步实现这个机制的协定叫做 Zab 协定
-
Zab 协定有两种模式:恢复模式(选主), 播送模式(同步)
- 当服务启动或者在领导者解体后,Zab 就进入了恢复模式, 当领导者被选举进去, 且大多数 Server 实现了和 leader 的状态同步当前, 恢复模式就完结了
- 状态同步保障了 leader 和 Server 具备雷同的零碎状态
- 为了保障事务的程序一致性,zookeeper 采纳了 递增的事务 id 号 (zxid) 来标识事务
- 所有的提议 (proposal) 都在被提出的时候加上了 zxid. 实现中 zxid 是一个 64 位的数字, 它高 32 位是 epoch 用来标识 leader 关系是否扭转, 每次一个 leader 被选出来, 它都会有一个新的 epoch, 标识以后属于那个 leader 的统治期间. 低 32 位用于递增计数
- 每个 Server 在工作过程中有三种状态:
- LOOKING: 以后 Server 不晓得 leader 是谁, 正在搜查
- LEADING: 以后 Server 即为选举进去的 leader
-
FOLLOWING: leader 曾经选举进去, 以后 Server 与之同步
选主流程
- 当 leader 解体或者 leader 失去大多数的 follower 这时候 Zookeeper 进入恢复模式
- 恢复模式须要从新选举出一个新的 leader, 让所有的 Server 都复原到一个正确的状态.
-
Zookeeper 的选举算法有两种:零碎默认的选举算法为 fast paxos
- 基于 fast paxos 算法
- 基于 basic paxos 算法
- 基于 fast paxos 算法:
fast paxos 流程是在选举过程中, 某 Server 首先向所有 Server 提议本人要成为 leader, 当其它 Server 收到提议当前, 解决 epoch 和 zxid 的抵触, 并承受对方的提议, 而后向对方发送承受提议实现的音讯, 反复这个流程, 最初肯定能选举出 Leader -
基于 basic paxos 算法:
- 选举线程由以后 Server 发动选举的线程负责, 其次要性能是对投票后果进行统计, 并选出举荐的 Server
- 选举线程首先向所有 Server 发动一次询问(包含本人)
- 选举线程收到回复后, 验证是否是本人发动的询问(验证 zxid 是否统一), 而后获取对方的 id(myid), 并存储到以后询问对象列表中, 最初获取对方提议的 leader 相干信息(id,zxid), 并将这些信息存储到当次选举的投票记录表中
- 收到所有 Server 回复当前, 就计算出 zxid 最大的那个 Server, 并将这个 Server 相干信息设置成下一次要投票的 Server;
- 线程将以后 zxid 最大的 Server 设置为以后 Server 要举荐的 Leader, 如果此时获胜的 Server 取得 n /2+ 1 的 Server 票数, 设置以后举荐的 leader 为获胜的 Server, 将依据获胜的 Server 相干信息设置本人的状态, 否则, 持续这个过程, 直到 leader 被选举进去
-
通过流程剖析咱们能够得出: 要使 Leader 取得少数 Server 的反对, 则 Server 总数必须是奇数 2n+1, 且存活的 Server 的数目不得少于 n +1. 每个 Server 启动后都会反复以上流程. 在恢复模式下, 如果是刚从解体状态复原的或者刚启动的 server 还会从磁盘快照中复原数据和会话信息,Zookeeper 会记录事务日志并定期进行快照, 不便在复原时进行状态复原. 选主的具体流程图如下所示:
同步流程
-
选完 leader 当前,Zookeeper 就进入状态同步过程:
- leader 期待 server 连贯
- Follower 连贯 leader, 将最大的 zxid 发送给 leader
- Leader 依据 follower 的 zxid 确定同步点
- 实现同步后告诉 follower 曾经成为 uptodate 状态
-
Follower 收到 uptodate 音讯后, 又能够从新承受 client 的申请进行服务
工作流程
-
Leader 工作流程:
Leader 次要有三个性能:- 复原数据
- 维持与 Learner 的心跳, 接管 Learner 申请并判断 Learner 的申请音讯类型
-
Learner 的音讯类型次要有 PING 音讯,REQUEST 音讯,ACK 音讯,REVALIDATE 音讯, 依据不同的音讯类型, 进行不同的解决
- PING 音讯: Learner 的心跳信息
- REQUEST 音讯: Follower 发送的提议信息, 包含写申请及同步申请
- ACK 音讯: Follower 的对提议的回复. 超过半数的 Follower 通过, 则 commit 该提议
- REVALIDATE 音讯: 用来缩短 SESSION 无效工夫
- Leader 的工作流程简图如下所示, 在理论实现中, 流程要比下图简单得多, 启动了三个线程来实现性能:
- Follower 工作流程:
-
Follower 次要有四个性能:
- 向 Leader 发送申请(PING 音讯,REQUEST 音讯,ACK 音讯,REVALIDATE 音讯)
- 接管 Leader 音讯并进行解决
- 接管 Client 的申请, 如果为写申请, 发送给 Leader 进行投票
- 返回 Client 后果
-
Follower 的音讯循环解决如下几种来自 Leader 的音讯:
- PING 音讯: 心跳音讯
- PROPOSAL 音讯: Leader 发动的提案, 要求 Follower 投票
- COMMIT 音讯: 服务器端最新一次提案的信息
- UPTODATE 音讯: 表明同步实现
- REVALIDATE 音讯: 依据 Leader 的 REVALIDATE 后果, 敞开待 revalidate 的 session 还是容许其承受音讯
- SYNC 音讯: 返回 SYNC 后果到客户端, 这个音讯最后由客户端发动, 用来强制失去最新的更新
- Follower 的工作流程简图如下所示,在理论实现中,Follower 是通过 5 个线程来实现性能的:
-
observer 流程和 Follower 的惟一不同的中央就是 observer 不会加入 leader 发动的投票
Zookeeper 利用场景
配置管理
- 集中式的配置管理在利用集群中是十分常见的, 个别都会实现一套集中的配置管理核心, 应答不同的利用集群对于共享各自配置的需要, 并且在配置变更时可能告诉到集群中的每一个机器, 也能够细分进行分层级监控
-
Zookeeper 很容易实现这种集中式的配置管理, 比方将 APP1 的所有配置配置到 /APP1 znode 下,APP1 所有机器一启动就对 /APP1 这个节点进行监控(zk.exist(“/APP1”,true)), 并且实现回调办法 Watcher, 那么在 zookeeper 上 /APP1 znode 节点下数据发生变化的时候,每个机器都会收到告诉,Watcher 办法将会被执行, 那么利用再取下数据即可(zk.getData(“/APP1”,false,null))
集群治理
- 利用集群中, 咱们经常须要让每一个机器晓得集群中 (或依赖的其余某一个集群) 哪些机器是活着的, 并且在集群机器因为宕机, 网络断链等起因可能不在人工染指的状况下迅速告诉到每一个机器
-
Zookeeper 同样很容易实现这个性能, 比方我在 zookeeper 服务器端有一个 znode 叫 /APP1SERVERS, 那么集群中每一个机器启动的时候都去这个节点下创立一个 EPHEMERAL 类型的节点, 比方 server1 创立 /APP1SERVERS/SERVER1(能够应用 ip, 保障不反复),server2 创立 /APP1SERVERS/SERVER2, 而后 SERVER1 和 SERVER2 都 watch /APP1SERVERS 这个父节点, 那么也就是这个父节点下数据或者子节点变动都会告诉对该节点进行 watch 的客户端. 因为 EPHEMERAL 类型节点有一个很重要的个性, 就是客户端和服务器端连贯断掉或者 session 过期就会使节点隐没, 那么在某一个机器挂掉或者断链的时候, 其对应的节点就会隐没, 而后集群中所有对 /APP1SERVERS 进行 watch 的客户端都会收到告诉, 而后获得最新列表即可
- 另外有一个利用场景就是集群选 master: 一旦 master 挂掉可能马上能从 slave 中选出一个 master, 实现步骤和前者一样, 只是机器在启动的时候在 APP1SERVERS 创立的节点类型变为 EPHEMERAL_SEQUENTIAL 类型, 这样每个节点会主动被编号
-
咱们默认规定编号最小的为 master, 所以当咱们对 /APP1SERVERS 节点做监控的时候, 失去服务器列表, 只有所有集群机器逻辑认为最小编号节点为 master, 那么 master 就被选出, 而这个 master 宕机的时候, 相应的 znode 会隐没, 而后新的服务器列表就被推送到客户端, 而后每个节点逻辑认为最小编号节点为 master, 这样就做到动静 master 选举
Zookeeper 监督
-
Zookeeper 所有的 读操作 -getData(),getChildren(), 和 exists() 都能够设置监督 (watch), 监督事件能够了解为一次性的触发器. 官网定义如下:a watch event is one-time trigger, sent to the client that set the watch, which occurs when the data for which the watch was set changes:
-
One-time trigger(一次性触发)
- 当设置监督的数据产生扭转时, 该监督事件会被发送到客户端
- 例如: 如果客户端调用了 getData(“/znode1”, true)并且稍后 /znode1 节点上的数据产生了扭转或者被删除了, 客户端将会获取到 /znode1 发生变化的监督事件, 而如果 /znode1 再一次产生了变动, 除非客户端再次对 /znode1 设置监督, 否则客户端不会收到事件告诉
-
Sent to the client(发送至客户端)
- Zookeeper 客户端和服务端是通过 socket 进行通信的, 因为网络存在故障, 所以监督事件很有可能不会胜利地达到客户端, 监督事件是异步发送至监督者的
- Zookeeper 自身提供了保序性(ordering guarantee): 即客户端只有首先看到了监督事件后, 才会感知到它所设置监督的 znode 产生了变动(a client will never see a change for which it has set a watch until it first sees the watch event). 网络提早或者其余因素可能导致不同的客户端在不同的时刻感知某一监督事件, 然而不同的客户端所看到的所有具备统一的程序
-
The data for which the watch was set(被设置 watch 的数据)
- znode 节点自身具备不同的改变方式
- 例如:Zookeeper 保护了两条监督链表: 数据监督和子节点监督 (data watches and child watches) getData() and exists() 设置数据监督,getChildren()设置子节点监督
- 又例如:Zookeeper 设置的不同监督返回不同的数据,getData()和 exists()返回 znode 节点的相干信息, 而 getChildren()返回子节点列表. 因而,setData()会触发设置在某一节点上所设置的数据监督 (假设数据设置胜利), 而一次胜利的 create() 操作则会登程以后节点上所设置的数据监督以及父节点的子节点监督. 一次胜利的 delete()操作将会触发以后节点的数据监督和子节点监督事件, 同时也会触发该节点父节点的 child watch
-
-
Zookeeper 中的监督是轻量级的, 因而容易设置, 保护和散发. 当客户端与 Zookeeper 服务器端失去分割时, 客户端并不会收到监督事件的告诉, 只有当客户端从新连贯后, 若在必要的状况下, 以前注册的监督会从新被注册并触发, 对于开发人员来说这通常是通明的. 只有一种状况会导致监督事件的失落, 即: 通过 exists()设置了某个 znode 节点的监督, 然而如果某个客户端在此 znode 节点被创立和删除的工夫距离内与 zookeeper 服务器失去了分割, 该客户端即便稍后从新连贯 zookeeper 服务器后也得不到事件告诉
Eureka(服务发现框架)
-
Eureka 是一个基于 REST 的服务, 次要用于定位运行在 AWS 域中的中间层服务,以达到负载平衡和中间层服务故障转移的目标. SpringCloud 将它集成在其子我的项目 spring-cloud-netflix 中,以实现 SpringCloud 的服务发现性能
Eureka 的两个组件
- Eureka Server: Eureka Server 提供服务注册服务,各个节点启动后,会在 Eureka Server 中进行注册,这样 EurekaServer 中的服务注册表中将会存储所有可用服务节点的信息,服务节点的信息能够在界面中看到. Eureka Server 之间通过复制的形式实现数据的同步
- Eureka Client: 是一个 java 客户端,用于简化与 Eureka Server 的交互,客户端同时也就是一个内置的、应用轮询 (round-robin) 负载算法的负载均衡器
-
Eureka 通过心跳查看、客户端缓存等机制,确保了零碎的高可用性、灵活性和可伸缩性
- 在利用启动后,将会向 Eureka Server 发送心跳, 如果 Eureka Server 在多个心跳周期内没有接管到某个节点的心跳,Eureka Server 将会从服务注册表中把这个服务节点移除。
-
Eureka 还提供了客户端缓存机制,即便所有的 Eureka Server 都挂掉,客户端仍然能够利用缓存中的信息生产其余服务的 API。Eureka 通过心跳查看、客户端缓存等机制,确保了零碎的高可用性、灵活性和可伸缩性
作业调度框架 -Quartz
Quartz 作业调度框架概念
- Quartz 是一个齐全由 java 编写的开源作业调度框架, 是 OpenSymphony 开源组织在 Job scheduling 畛域的开源我的项目, 它能够与 J2EE 与 J2SE 应用程序相结合也能够独自应用,Quartz 框架整合了许多额定性能.Quartz 能够用来创立简略或运行十个,百个,甚至是好几万个 Jobs 这样简单的程序
-
Quartz 三个次要的概念:
-
调度器:
- Quartz 框架的外围是调度器
- 调度器负责管理 Quartz 利用运行时环境
- 调度器不是靠本人做所有的工作, 而是依赖框架内一些十分重要的部件
- Quartz 怎么能并发运行多个作业的原理: Quartz 不仅仅是线程和线程池治理, 为确保可伸缩性,Quartz 采纳了基于多线程的架构. 启动时, 框架初始化一套 worker 线程, 这套线程被调度器用来执行预约的作业.
- Quartz 依赖一套松耦合的线程池治理部件来治理线程环境
-
工作:
- 本人编写的业务逻辑, 交给 quartz 执行
-
触发器:
-
调度作业, 什么时候开始执行, 什么时候完结执行
Quartz 设计模式
-
-
- Builer 模式
- Factory 模式
- 组件模式
-
链式写法
Quartz 体系结构
Quartz 框架中的外围类:
-
JobDetail:
- Quartz 每次运行都会间接创立一个 JobDetail, 同时创立一个 Job 实例.
- 不间接承受一个 Job 的实例, 承受一个 Job 的实现类
- 通过 new instance()的反射形式来实例一个 Job, 在这里 Job 是一个接口, 须要编写类去实现这个接口
-
Trigger:
- 它由 SimpleTrigger 和 CronTrigger 组成
- SimpleTrigger 实现相似 Timer 的定时调度工作,CronTrigger 能够通过 cron 表达式实现更简单的调度逻辑
-
Scheduler:
- 调度器
-
JobDetail 和 Trigger 能够通过 Scheduler 绑定到一起
Quartz 重要组件
Job 接口
-
能够通过实现该接口来实现咱们本人的业务逻辑, 该接口只有 execute()一个办法, 咱们能够通过上面的形式来实现 Job 接口来实现咱们本人的业务逻辑
public class HelloJob implements Job{public void execute(JobExecutionContext context) throws JobExecutionException {// 编写咱们本人的业务逻辑}
JobDetail
-
每次都会间接创立一个 JobDetail, 同时创立一个 Job 实例, 它不间接承受一个 Job 的实例, 然而它承受一个 Job 的实现类, 通过 new instance()的反射形式来实例一个 Job. 能够通过上面的形式将一个 Job 实现类绑定到 JobDetail 中
JobDetail jobDetail=JobBuilder.newJob(HelloJob.class). withIdentity("myJob", "group1") .build();
JobBuiler
-
次要是用来创立 JobDeatil 实例
JobStore
-
绑定了 Job 的各种数据
Trigger
-
次要用来执行 Job 实现类的业务逻辑的,咱们能够通过上面的代码来创立一个 Trigger 实例
CronTrigger trigger = (CronTrigger) TriggerBuilder .newTrigger() .withIdentity("myTrigger", "group1") // 创立一个标识符 .startAt(date)// 什么时候开始触发 // 每秒钟触发一次工作 .withSchedule(CronScheduleBuilder.cronSchedule("* * * * * ? *")) .build();
Scheduler
创立 Scheduler 有两种形式
-
通过 StdSchedulerFactory 来创立
SchedulerFactory sfact=new StdSchedulerFactory(); Scheduler scheduler=sfact.getScheduler();
-
通过 DirectSchedulerFactory 来创立
DiredtSchedulerFactory factory=DirectSchedulerFactory.getInstance(); Scheduler scheduler=factory.getScheduler();
Scheduler 配置参数个别存储在 quartz.properties 中, 咱们能够批改参数来配置相应的参数. 通过调用 getScheduler() 办法就能 创立和初始化调度对象
-
Scheduler 的次要函数:
- Date schedulerJob(JobDetail,Trigger trigger): 返回最近触发的一次工夫
- void standby(): 临时挂起
- void shutdown(): 齐全敞开, 不能重新启动
- shutdown(true): 示意期待所有正在执行的 job 执行结束之后, 再敞开 scheduler
- shutdown(false): 间接敞开 scheduler
-
quartz.properties 资源文件:
在 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
-
quartz.properties 资源文件次要组成部分:
- 调度器属性
- 线程池属性
- 作业存储设置
- 插件设置
-
调度器属性:
- org.quartz.scheduler.instanceName 属性用来辨别特定的调度器实例, 能够依照性能用处来给调度器起名
- org.quartz.scheduler.instanceId 属性和前者一样, 也容许任何字符串, 但这个值必须是在所有调度器实例中是惟一的, 尤其是在一个集群当中, 作为集群的惟一 key. 如果想 quartz 生成这个值的话, 能够设置为 Auto
-
线程池属性:
- threadCount: 设置线程的数量
- threadPriority: 设置线程的优先级
- org.quartz.threadPool.class: 线程池的实现
-
作业存储设置:
- 形容了在调度器实例的申明周期中,job 和 trigger 信息是怎么样存储的
-
插件配置:
-
满足特定需要用到的 quartz 插件的配置
监听器
对事件进行监听并且退出本人相应的业务逻辑, 次要有以下三个监听器别离对 Job,Trigger,Scheduler 进行监听:
-
- JobListener
- TriggerListener
-
SchedulerListener
Cron 表达式
字段 | 允许值 | 容许特殊字符 |
---|---|---|
秒 | 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 点, 每个整点执行一次
接口测试框架 -Swagger2
Swagger 介绍
- Swagger是一款 RESTful 接口的文档在线生成和接口测试工具
- Swagger是一个标准残缺的框架, 用于生成, 形容, 调用和可视化 RESTful 格调的 web 服务
- 总体目标是使客户端和文件系统作为服务器以同样的速度更新
-
文件的办法, 参数和模型严密集成到服务器端代码, 容许 API 始终保持同步
Swagger 作用
- 接口文档在线主动生成
-
功能测试
Swagger 次要我的项目
- Swagger-tools: 提供各种与 Swagger 进行集成和交互的工具. 比方 Swagger Inspector,Swagger Editor
- Swagger-core: 用于 Java 或者 Scala 的 Swagger 实现, 与 JAX-RS,Servlets 和 Play 框架进行集成
- Swagger-js: 用于 JavaScript 的 Swagger 实现
- Swagger-node-express: Swagger 模块, 用于 node.js 的 Express Web 利用框架
- Swagger-ui: 一个无依赖的 html,js 和 css 汇合, 能够为 Swagger 的 RESTful API 动静生成文档
-
Swagger-codegen: 一个模板驱动引擎, 通过剖析用户 Swagger 资源申明以各种语言生成客户端代码
Swagger 工具
-
Swagger Codegen:
- 通过 Codegen 能够将形容文件生成 html 格局和 cwiki 模式的接口文档, 同时也能生成多种语言的服务端和客户端的代码
- 反对通过 jar 包 ,docker,node等形式在本地化执行生成, 也能够在前面 Swagger Editor 中在线生成
-
Swagger UI:
- 提供一个可视化的 UI 页面展现形容文件
- 接口的调用方, 测试, 项目经理等都能够在该页面中对相干接口进行查阅和做一些简略的接口申请
- 该我的项目反对在线导入形容文件和本地部署 UI 我的项目
-
Swagger Editor:
- 相似于 markdown 编辑器用来编辑 Swagger 形容文件的编辑器
- 该编辑器反对实时预览形容文件的更新成果
- 提供了在线编辑器和本地部署编辑器两种形式
-
Swagger Inspector:
- 在线对接口进行测试
- 会比 Swagger 外面做接口申请会返回更多的信息, 也会保留申请的理论申请参数等数据
-
Swagger Hub:
- 集成下面的所有工具的各个性能
-
能够以我的项目和版本为单位, 将形容文件上传到 Swagger Hub 中, 在 Swagger Hub 中能够实现下面我的项目的所有工作
Swagger 注解
@Api
该注解将一个 controller 类标注为一个 Swagger API. 在默认状况下 ,Swagger core 只会扫描解析具备 @Api注解的类, 而疏忽其它类别的资源, 比方 JAX-RS endpoints, Servlets 等注解. 该注解的属性有:
- tags: API 分组标签, 具备雷同标签的 API 将会被归并在一组内显示
-
value: 如果 tags 没有定义 ,value将作为 Api 的tags应用
@ApiOperation
在指定接口门路上, 对一个操作或者 http 办法进行形容. 具备雷同门路的不同操作会被归组为同一个操作对象. 紧接着是不同的 http 申请办法注解和门路组合形成一个惟一操作. 该注解的属性有:
- value: 对操作进行简略阐明
- notes: 对操作进行具体阐明
- httpMethod: http 申请动作名, 可选值有 :GET, HEAD, POST, PUT, DELETE, OPTIONS, PATCH
-
code: 胜利操作后的返回类型. 默认为 200, 参照规范 Http Status Code Definitions
@ApiParam
减少对参数的元信息阐明, 紧接着应用 Http 申请参数注解. 次要属性有:
- required: 是否为必传参数
-
value: 参数简短阐明
@ApiResponse
形容一个操作的可能返回后果. 当 RESTful 申请产生时, 这个注解可用于形容所有可能的胜利与错误码. 能够应用也能够不应用这个注解去形容操作返回类型. 但胜利操作后的返回类型必须在 @ApiOperation中定义. 如果 API 具备不同的返回类型, 那么须要别离定义返回值, 并将返回类型进行关联. 然而 Swagger 不反对同一返回码, 多种返回类型的注解. 这个注解必须被蕴含在 @ApiResponses中:
- code: http 申请返回码, 参照规范 Http Status Code Definitions
- message: 更加易于了解的文本音讯
- response: 返回类型信息, 必须应用齐全限定类名, 即类的残缺门路
-
responseContainer: 如果返回值类型为容器类型, 能够设置相应的值. 有效值 :List, Set, Map. 其它的值将会被疏忽
@ApiResponses
注解 @ApiResponse的包装类, 数组构造. 即便须要应用一个 @ApiResponse注解, 也须要将 @ApiResponse注解蕴含在注解 @ApiResponses内
@ApiImplicitParam
对 API 的繁多参数进行注解. 注解 @ApiParam须要同 JAX-RS 参数相绑定, 但这个 @ApiImplicitParam注解能够以对立的形式定义参数列表, 这是在 Servlet 和非 JAX-RS 环境下惟一的形式参数定义形式. 留神这个注解 @ApiImplicitParam必须被蕴含在注解 @ApiImplicitParams之内, 能够设置以下重要属性:
- name: 参数名称
- value: 参数简短形容
- required: 是否为必传参数
- dataType: 参数类型, 能够为类名, 也能够为根本类型, 比方 String,int,boolean 等
-
paramType: 参数的申请类型, 可选的值有path, query, body, header, from
@ApiImplicitParams
注解 @ApiImplicitParam的容器类, 以数组形式存储
@ApiModel
提供对 Swagger model 额定信息的形容. 在标注 @ApiOperation注解的操作内, 所有类将主动 introspected. 利用这个注解能够做一些更具体的model 构造阐明. 次要属性值有:
- value: model 的别名, 默认为类名
-
description: model 的详细描述
@ApiModelProperty
对 model 属性的注解, 次要属性值有:
- value: 属性简短形容
- example: 属性示例值
-
required: 是否为必须值
数据库版本控制 -Liquibase,flyway
Liquibase
Liquibase 基本概念
- Liquibase是一个用于跟踪, 治理和利用数据库变动的数据重构和迁徙的开源工具, 通过日志文件的模式记录数据库的变更, 而后执行日志文件中的批改, 将数据库更新或回滚到统一的状态
-
Liquibase 的次要特点:
- 不依赖于特定的数据库, 反对所有支流的数据库. 比方 MySQL, PostgreSQL, Oracle, SQL Server, DB2 等. 这样在数据库的部署和降级环节能够帮忙利用零碎反对多数据库
- 提供数据库比拟性能, 比拟后果保留在 XML 中, 基于 XML 能够用 Liquibase 部署和降级数据库
- 反对多开发者的合作保护, 以 XML 存储数据库变动, 以 author 和id惟一标识一个changeSet, 反对数据库变动的合并
- 日志文件反对多种格局. 比方 XML, YAML, JSON, SQL 等
- 反对多种运行形式. 比方 命令行, Spring 集成, Maven 插件, Gradle 插件 等
- 在数据库中保留数据库批改历史DatabaseChangeHistory, 在数据库降级时主动跳过已利用的变动
- 提供变动利用的回滚性能, 可按工夫, 数量或标签 tag 回滚曾经利用的变动
-
可生成 html 格局的数据库批改文档
日志文件 changeLog
- changeLog是 Liquibase 用来记录数据库变更的日志文件, 个别放在 classpath 下, 而后配置到执行门路中
- changeLog反对多种格局, 次要有 XML, JSON, YAML, SQL, 举荐应用XML 格局
- 一个 < changeSet > 标签对应一个变更集, 由属性 id, name, changelog 的文件门路惟一标识 组合而成
- changelog在执行时不是依照 id 的程序, 而是依照 changSet 在changlog中呈现的程序
- 在执行 changelog 时 ,Liquibase会在数据库中新建 2 张表, 写执行记录:databasechangelog – changelog 的执行日志 和databasechangeloglock – changelog 锁日志
- 在执行 changelog 中的 changeSet 时, 会首先查看 databasechangelog 表, 如果曾经执行过, 则会跳过, 除非 changeSet 的runAlways属性为 true, 如果没有执行过, 则执行并记录changelog 日志
-
changelog中的一个 changeSet 对应一个事务, 在 changeSet 执行完后commit, 如果呈现谬误就会rollback
罕用标签及命令
changeSet 标签
< changeSet > 标签的次要属性有:
-
runAlways: 即便执行过, 依然每次都要执行
- 因为 databasechangelog 中还记录了 changeSet 的MD5校验值 MD5SUM, 如果changeSet 的id和 name 没变, 而内容变动. 则 MD5 值变动, 这样即便 runAlways 的值为true, 也会导致执行失败报错.
- 这时应该应用 runOnChange 属性
- runOnChange: 第一次的时候以及当 changeSet 发生变化的时候执行, 不受 MD5 校验值的束缚
-
runInTransaction: 是否作为一个事务执行, 默认为true.
- 如果设置为false, 须要留神: 如果执行过程中出错了不会rollback, 会导致数据库处于不统一的状态
< changeSet > 有一个 < rollback > 子标签, 用来定义回滚语句:
- 对于 create table, rename column, add column 等 ,Liquibase会主动生成对应的 rollback 语句
-
对于 drop table, insert data 等须要显式定义 rollback 语句
include 标签
-
当 changelog 文件越来越多时, 须要应用 < include > 标签将文件治理起来:
- file: 蕴含的 changelog 文件的门路, 这个文件能够是 Liquibase 反对的任意格局
-
relativeToChangelogFile: 绝对于 changelogFile 的门路, 示意 file 属性的文件门路是绝对于 changelogFile 的而不是 classpath 的, 默认为false
<?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>
-
< include > 标签存在循环援用和反复援用的问题, 循环援用会导致有限循环, 须要留神
includeAll 标签
-
< includeAll > 标签指定的是 changelog 的目录, 而不是文件
<includeAll path="com/example/changelogs/"/>
diff 命令
-
diff 命令用于比拟数据库之间的异同
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
generateChangeLog
-
在已有我的项目上应用LiquiBase, 须要生成以后数据的changeSet, 能够应用两种形式:
- 应用数据库工具导出 SQL 数据, 而后在 changLog 文件中以 SQL 格局记录
-
应用 generateChangeLog 命令生成 changeLog 文件
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不反对存储过程, 函数以及触发器
Liquibase 应用示例
-
在 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
-
flyway
flyway 基本概念
-
- flyway是一款数据库版本控制管理工具, 反对数据库版本主动降级, 不仅反对 Command Line 和Java API, 同时也反对 Build 构建工具和SpringBoot, 也能够在分布式环境下安全可靠地降级数据库, 同时也反对失败复原
-
flyway是一款数据库迁徙 (migration) 工具, 也就是在部署利用的时候, 执行数据库脚本的利用, 反对 SQL 和Java两种类型的脚本, 能够将这些脚本打包到应用程序中, 在应用程序启动时, 由 flyway 来治理这些脚本的执行, 这些脚本在 flyway 中叫作migration
-
没有应用 flyway 时部署利用的流程:
- 开发人员将程序利用打包, 按程序汇总并整顿数据库降级脚本
- DBA 拿到数据库降级脚本查看, 备份, 执行, 以实现数据库降级
- 利用部署人员拿到利用部署包, 备份, 替换, 实现应用程序降级
-
引入 flyway 时部署利用的流程:
- 开发人员将程序打包
- 利用部署人员拿到利用部署包, 备份, 替换, 实现应用程序降级. 期间 flyway 主动执行降级, 备份脚本
-
- flyway的外围: MetaData 表 – 用于记录所有版本演变和状态
-
flyway首次启动会创立默认名为 SCHMA_VERSION 表, 保留了 版本, 形容和要执行的 SQL 脚本
flyway 次要个性
- 一般 SQL: 纯 SQL 脚本, 包含占位符替换, 没有专有的 XML 格局
- 无限度: 能够通过 Java 代码实现高级数据操作
- 零依赖: 只需运行在 Java 6 以上版本及数据库所需的 JDBC 驱动
- 约定大于配置: 数据库迁徙时, 主动查找系统文件和类门路中的 SQL 文件或 Java 类
- 高可靠性: 在集群环境下进行数据库的降级是安全可靠的
- 云反对: 齐全反对 Microsoft SQL Azure, Google Cloud SQL & App Engine, Heroku Postgres 和Amazon RDS
- 主动迁徙: 应用 flyway 提供的API, 能够让利用启动和数据库迁徙同时工作
- 疾速失败: 损坏的数据库或失败的迁徙能够避免应用程序启动
-
数据库清理: 在一个数据库中删除所有的表, 视图, 触发器. 而不是删除数据库自身
SQL 脚本
-
格局 : V + 版本号 + 双下划线 + 形容 + 结束符
V1_INIT_DATABASE.sql
-
V是默认值, 能够进行自定义配置:
flyway.sql-migration-prefix= 指定前缀
flyway 工作原理
-
flyway 对数据库进行版本治理次要由 Metadata 表和 6 种命令 : Migrate, Clean, Info, Validate, Undo, Baseline, Repair实现
- 在一个空数据库上部署集成 flyway 利用:
- 应用程序启动时 ,flyway在这个数据库中创立一张表, 用于记录 migration 的执行状况, 表名默认为:schema_version:
- 而后 ,flyway依据表中的记录决定是否执行利用程序包中提供的migration:
- 最初, 将执行后果写入 schema_version 中并校验执行后果:
- 下次版本迭代时, 提供新的 migration, 会依据schema_version 的记录执行新的migration:
flyway 外围
Metadata Table
- 在一个空数据库上部署集成 flyway 利用:
- flyway 中最外围的就是用于记录所有版本演变和状态的 Metadata 表
- 在 flyway 首次启动时会创立默认表名为 SCHEMA_VERSION 的元数据表, 表构造如下:
列名 | 类型 | 是否为 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将每一个数据库脚本称之为 migration,flyway 次要反对两种类型的migrations:
-
Versioned migrations:
- 最罕用的 migration, 用于版本升级
- 每一个版本都有一个惟一的标识并且只能被利用一次, 并且不能再批改曾经加载过的 Migrations, 因为 Metadata 表会记录 Checksum 值
- version 标识版本号由一个或多个数字形成, 数字之间的分隔符能够采纳点或下划线, 在运行时下划线其实也是被替换成点了, 每一部分的前导数字 0 都会被主动疏忽
-
Repeatable migrations:
- 指的是可反复加载的 Migrations, 每一次的更新会影响 Checksum 值, 而后都会被从新加载, 并不用于版本升级. 对于治理不稳固的数据库对象更新时十分有用
- Repeatable 的 Migrations 总是在 Versioned 的 Migrations 之后按程序执行, 开发者须要保护脚本并且确保能够反复执行. 通常会在 sql 语句中应用 CREATE OR REPLACE 来确保可反复执行
-
-
Migration 命名标准:
- prefix: 前缀标识. 能够配置, 默认状况下: V – Versioned, R – Repeatable
- version: 标识版本号. 由一个或多个数字形成, 数字之间的分隔符能够应用点或者下划线
- separator: 用于宰割标识版本号和形容信息. 可配置, 默认状况下是两个下划线
- description: 形容信息. 文字之间能够用下划线或空格宰割
-
suffix: 后续标识. 可配置, 默认为 .sql
- 确保版本号惟一 ,flyway依照版本号程序执行 . repeatable没有版本号, 因为 repeatable migration 会在内容扭转时反复执行
- 默认状况下 ,flyway会将单个 migration 放在一个事务里执行, 也能够通过配置将所有 migration 放在同一个事务里执行
-
每个 Migration 反对两种编写形式:
- Java API
-
SQL 脚本
-
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");
Callbacks
-
- flyway在执行 migration 时提供了一系列的hook, 能够在执行过程中进行额定的操作:
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 |
-
只有将 migration 的名称以 hook 结尾, 这些 hook 就能够执行 SQL 和 Java 类型的 migrations:
-
SQL 类型的 hook:
- beforeMigrate.sql
- beforeEachMigrate.sql
- beforeRepair_vacuum.sql
-
Java 类型的 hook 须要实现接口 : org.flyway.core.api.callback.CallBack
flyway 中 6 种命令
-
-
Migrate:
- 将数据库迁徙到最新版本, 是 flyway 工作流的外围性能.
- flyway在 Migrate 时会查看元数据 Metadata 表. 如果不存在会创立 Metadata 表,Metadata 表次要用于记录版本变更历史以及 Checksum 之类
- 在 Migrate 时会扫描指定文件系统或 classpath 下的数据库的版本脚本 Migrations, 并且会逐个比对Metadata 表中曾经存在的版本记录, 如果未利用的 Migrations,flyway 会获取这些 Migrations 并按秩序 Apply 到数据库中, 否则不会做任何事件
- 通常会在应用程序启动时默认执行 Migrate 操作, 从而防止程序和数据库的不统一
-
Clean:
- 来革除掉对应数据库的 Schema 的所有对象 .flyway不是删除整个数据库, 而是革除所有 表构造, 视图, 存储过程, 函数以及所有相干的数据
- 通常在开发和测试阶段应用, 可能疾速无效地更新和从新生成数据库表构造. 然而不应该在 production 的数据库应用
-
Info:
- 打印所有 Migrations 的具体和状态信息, 是通过 Metadata 表和 Migrations 实现的
- 可能疾速定位以后数据库版本, 以及查看执行胜利和失败的Migrations
-
Validate:
- 验证曾经 Apply 的Migrations是否有变更 ,flyway是默认开启验证的
- 操作原理是比照 Metadata 表与本地 Migration 的Checksum值, 如果雷同则验证通过, 否则验证失败, 从而能够避免对曾经 Apply 到数据库的本地 Migrations 的无心批改
-
Baseline:
- 针对曾经存在 Schema 构造的数据库的一种解决方案
- 实现在非空数据库中新建 Metadata 表, 并将 Migrations 利用到该数据库
- 能够利用到特定的版本, 这样在已有表构造的数据库中也能够实现增加 Metadata 表, 从而利用 flyway 进行新的 Migrations 的治理
-
Repair:
- 修复 Metadata 表, 这个操作在 Metadata 表体现谬误时很有用
-
通常有两种用处:
- 移除失败的 Migration 记录, 这个问题针对不反对 DDL 事务的数据库
-
从新调整曾经利用的 Migrations 的Checksums的值. 比方, 某个 Migration 曾经被利用, 但本地进行了批改, 又冀望从新利用并调整 Checksum 值. 不倡议对数据库进行本地批改
flyway 的应用
正确创立 Migrations
-
Migrations: flyway 在更新数据库时应用的版本脚本
- 一个基于 sql 的Migration命名为 V1_ _init_tables.sql, 内容即为创立所有表的sql 语句
- flyway也反对基于 Java 的Migration
- flyway加载 Migrations 的默认 Locations 为classpath:db/migration, 也能够指定 filesystem:/project/folder. Migrations 的加载是在运行时主动递归执行的
-
除了指定的 Locations 外,flyway 须要听从命名格局对 Migrations 进行扫描, 次要分为两类:
-
Versioned migrations:
- Versioned类型是罕用的 Migration 类型
- 用于版本升级, 每一个版本都有一个惟一的标识并且只能被利用一次. 并且不能再批改曾经加载过的 Migrations, 因为Metadata 表会记录 Checksum 值
- 其中的 version 标识版本号, 由一个或者多个数字形成, 数字之间的分隔符能够采纳点或者下划线, 在运行时下划线也是被替换成点了. 每一部分的前导零都会被省略
-
Repeatable migrations:
- Repeatable是指可反复加载的 Migrations, 其中每一次更新都会更新Checksum 值, 而后都会被从新加载, 并不用于版本升级. 对于治理不稳固的数据库对象的更新时十分有用
-
Repeatable的 Migrations 总是在 Versioned 之后按程序执行, 开发者须要保护脚本并确保能够反复执行, 通常会在 sql 语句中应用 CREATE OR REPLACE 来保障可反复执行
flyway 数据库
-
-
flyway 反对多种数据库:
- Oracle
- SQL Server
- SQL Azure
- DB2
- DB2 z/OS
- MySQL
- Amazon RDS
- Maria DB
- Google Cloud SQL
- PostgreSQL
- Heroku
- Redshift
- Vertica
- H2
- Hsql
- Derby
- SQLite
- SAP HANA
- solidDB
- Sybase ASE and Phoenix
-
目前支流应用的数据库有 MySQL,H2,Hsql 和PostgreSQL. 对应的 flyway.url 配置如下:
# 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
flyway 命令行
- flyway命令行工具反对间接在命令行中运行 Migrate,Clean,Info,Validate,Baseline 和 Repair 这 6 种命令
-
flyway 会顺次搜寻以下配置文件:
- /conf/flyway.conf
- /flyway.conf
-
前面的配置会笼罩后面的配置
SpringBoot 集成 flyway
-
引入 flyway 依赖:
<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>
-
创立的 springboot 的 maven 我的项目, 配置数据源信息:
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
-
在 classpath 目录下新建 /db/migration 文件夹, 并创立 SQL 脚本:
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');
-
启动 springboot 我的项目:
- 在我的项目启动时 ,flyway加载了 SQL 脚本并执行
-
查看数据库:
- 默认状况下, 生成 flyway-schema-history 表
-
如果须要指定 schema 表的命名, 能够配置属性 : flyway.tableflyway
flyway 配置
属性名 默认值 形容 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 基本概念
-
Docker
- 是用于开发利用, 交付利用, 运行利用的开源软件的一个开放平台
- 容许用户将基础设施中的利用独自宰割进去, 造成更细小的容器, 从而提交交付软件的速度
-
Docker 容器:
-
相似虚拟机, 不同点是:
- Docker 容器是将 操作系统层 虚拟化
- 虚拟机则是虚拟化 硬件
- Docker容器更具备便携性, 可能高效地利用服务器
- 容器更多的是用于示意软件的一个标准化单元, 因为容器的标准化, 因而能够忽视基础设施的差别, 部署到任何一个中央
- Docker也为容器提供更强的业界隔离兼容
-
-
Docker利用 Linux 内核中的资源拆散机制 cgroups 以及 Linux 内核的 namespace 来创立独立的容器containers
- 能够在 Linux 实体下运作, 防止疏导一个虚拟机造成的额外负担
-
Linux内核对 namespace 的反对能够齐全隔离工作环境下的应用程序, 包含:
- 线程树
- 网络
- 用户 ID
- 挂载文件系统
-
Linux内核的 cgroups 提供资源隔离, 包含:
- CPU
- 存储器
- block I/O
-
网络
Docker 基础架构
Docker 引擎
-
Docker 引擎: Docker Engine
- 是一个服务端 – 客户端构造的利用
-
次要组成部分:
-
Docker 守护过程: Docker daemons, 也叫 dockerd.
- 是一个长久化过程, 用户治理容器
- Docker 守护过程会监听 Docker 引擎 API 的申请
-
Docker 引擎 API: Docker Engine API
- 用于与 Docker 守护过程交互应用的 API
- 是一个 RESTful API, 不仅能够被 Docker 客户端调用, 也能够被 wget 和 curl 等命令调用
-
Docker 客户端: docker
- 是大部分用户与 Docker 交互的次要形式
- 用户通过客户端将命令发送给守护过程
-
命令遵循 Docker Engine API
Docker 注册核心
-
- Docker 注册核心: Docker registry, 用于存储 Docker 镜像
-
Docker Hub: Docker 的公共注册核心, 默认状况下,Docker 在这里寻找镜像. 也能够自行构建公有的注册核心
Docker 对象
-
Docker对象指的是 :Images,Containers,Networks, Volumes,Plugins等等
-
镜像: Images
- 一个只读模板, 用于批示创立容器
- 镜像是分层构建的, 定义这些档次的文件叫作Dockerfile
-
容器: Containers
- 镜像可运行的实例
- 容器能够通过 API 或者 CLI(命令行)进行操作
-
服务: Services
- 容许用户逾越不同的 Docker 守护过程的状况下减少容器
-
并将这些容器分为管理者 (managers) 和工作者(workers), 来为 swarm 独特工作
Docker 扩大架构
Docker Compose
-
- Docker Compose是用来定义和运行多个容器 Docker 应用程序的工具
- 通过 Docker Compose, 能够应用YAML 文件来配置应用程序所须要的所有服务, 而后通过一个命令, 就能够创立并启动所有服务
-
Docker Compose对应的命令为 : docker-compose
Swarm Mode
- 从 Docker 1.12 当前 ,swarm mode集成到 Docker 引擎中, 能够应用 Docker 引擎 API 和CLI命令间接应用
-
Swarm Mode 内置 k-v 存储性能, 特点如下:
- 具备容错能力的去中心化设计
- 内置服务发现
- 负载平衡
- 路由网格
- 动静伸缩
- 滚动更新
- 平安传输
- Swarm Mode 的相干个性使得 Docker 本地的 Swarm 集群具备与 Mesos.Kubernetes 竞争的实力
-
cluster: 集群
- Docker 将集群定义为 – 一群独特作业并提供高可用性的机器
-
swarm: 群
-
一个集群的 Docker 引擎以 swarm mode 模式运行
- swarm mode是指 Docker 引擎内嵌的集群治理和编排性能
- 当初始化一个 cluster 中的 swarm 或者将节点退出一个 swarm 时 ,Docker引擎就会以 swarm mode 的模式运行
-
-
Swarm Mode 原理:
-
swarm中的 Docker 机器分为两类:
- managers: 管理者. 用于解决集群关系和委派
-
workers: 工作者. 用于执行 swarm 服务
- 当创立 swarm 服务时, 能够减少各种额定的状态: 数量, 网络, 端口, 存储资源等等
-
Docker 会去维持用户须要的状态:
- 比方, 一个工作节点宕机后, 那么 Docker 就会把这个节点的工作委派给另外一个节点
- 这里的工作 task 是指: 被 swarm 管理者治理的一个运行中的容器
-
-
swarm 绝对于独自容器的长处:
- 批改 swarm 服务的配置后无需重启
- Docker以 swarm mode 模式运行时, 能够抉择间接启动独自的容器
- 在 swarm mode 下, 能够通过 docker stack deploy 应用 Compose 文件部署利用栈
-
swarm服务分为两种:
- replicated services: 能够指定节点工作的总数量
- global services: 每个节点都会运行一个指定工作
- swarm管理员能够应用 ingress 负载平衡使服务能够被内部接触
-
swarm管理员会主动地给服务调配PublishedPort, 或者手动配置.
- 内部组件, 比方云负载均衡器能通过集群中任何节点上的 PublishedPort 去染指服务, 无论服务是否启动
- Swarm Mode有外部 DNS 组件, 会为每个服务调配一个 DNS 条目 . swarm管理员应用 internal load balancing 去散发申请时, 就是依附的这个 DNS 组件
-
Swarm Mode性能是由 swarmkit 提供的, 实现了 Docker 的编排层, 使得 swarm 能够间接被 Docker 应用
文件格式
-
Docker 有两种文件格式:
- Dockerfile: 定义了单个容器的内容和启动时候的行为
-
Compose 文件: 定义了一个多容器利用
Dockerfile
-
Docker 能够按照 Dockerfile 的内容, 自动化地构建镜像
-
Dockerfile蕴含着用户想要 如何构建镜像的所有命令的文本
FROM ubuntu:18.04 COPY . /app RUN make /app CMD python /app/app.py
-
RUN:
- RUN 会在以后镜像的顶层上增加新的一层 layer, 并在该层上执行命令, 执行后果将会被提交
- 提交后的后果将会利用于 Dockerfile 的下一步
-
ENTRYPOINT:
- 入口点
- ENTRYPOINT 容许配置容器, 使之成为可执行程序. 即 ENTRYPOINT 容许为容器减少一个入口点
- ENTRYPOINT 与 CMD 相似, 都是在容器启动时执行, 然而 ENTRYPOINT 的操作稳固并且不可被笼罩
- 通过在命令行中指定 – -entrypoint命令的形式, 能够在运行时将 Dockerfile 文件中的 ENTRYPOINT 笼罩
-
CMD:
- command的缩写
- CMD 用于为曾经创立的镜像提供默认的操作
- 如果不想应用 CMD 提供的默认操作, 能够应用docker run IMAGE [:TAG|@DIGEST] [COMMAND] 进行替换
-
当 Dockerfile 领有入口点的状况下,CMD 用于为入口点赋予参数
Compose 文件
-
-
Compose文件是一个 YAML 文件, 定义了 服务, 网络 和卷:
- service: 服务. 定义各容器的配置, 定义内容将以命令行参数的形式传给 docker run 命令
- network: 网络. 定义各容器的配置, 定义内容将以命令行参数的形式传给 docker network create 命令
- volume: 卷. 定义各容器的配置, 定义内容将以命令行参数的形式传给 docker volume create 命令
- docker run 命令中有一些选项, 和 Dockerfile 文件中的指令成果是一样的: CMD, EXPOSE, VOLUME, ENV. 如果 Dockerfile 文件中曾经应用了这些命令, 那么这些指令就被视为默认参数, 所以无需在 Compose 文件中再指定一次
-
Compose 文件中能够应用 Shell 变量:
db: image: "postgres:${POSTGRES_VERSION}"
-
Compse文件可通过本身的 ARGS 变量, 将参数传递给 Dockerfile 中的 ARGS 指令
网络
bridge
- Docker 中的网桥应用的软件模式的网桥
- 应用雷同的网桥的容器连贯进入该网络, 非该网络的容器无奈进入
- Docker 网桥驱动会主动地在 Docker 主机上安装规定, 这些规定使得不同桥接网络之间不能间接通信
-
桥接常常用于:
- 在独自容器上运行利用时, 能够通过网桥进行通信
- 网桥网络实用于容器运行在雷同的 Docker 守护过程的主机上
- 不同 Docker 守护过程主机上的容器之间的通信须要依附操作系统档次的路由, 或者能够应用 overlay 网络进行代替
-
bridge: 网桥驱动
- 是 Docker 默认的网络驱动, 接口名为docker0
- 当没有为容器指定一个网络时,Docker 将应用这个驱动
- 能够通过 daemon.json 文件批改相干配置
-
自定义网桥能够通过 brtcl 命令进行配置
host
-
host: 主机模式
- 用于独自容器, 该网络下容器只能和 Docker 主机进行间接连贯
- 这种 host 主机模式只实用于 Docker 17.06 当前版本的 swarm 服务
-
host 网络和 VirtualBox 的仅主机网络 Host-only Networking 相似
overlay
-
overlay: 笼罩模式
- 网络驱动将会创立分布式网络, 该网络能够笼罩若干个 Docker 守护过程主机
- overlay 是基于主机特定网络host-specific networks, 当加密性能开启时, 容许 swarm 服务和容器进行平安通信
- 在笼罩网络 overlay 下,Docker 可能清晰地把握数据包路由以及发送接管容器
-
overlay 有两种网络类型网络:
- ingress: 是可掌控 swarm 服务的网络流量, ingress 网络是 overlay 的默认网络
- docker_gwbridge: 网桥网络, docker_gwbridge 网络会将独自的 Docker 守护过程连贯至 swarm 里的另外一个守护过程
-
在 overlay 网络下:
- 独自的容器 和swarm 服务的行为和配置 概念是不一样的
-
overlay 策略不须要容器具备操作系统级别的路由, 因为 Docker 负责路由
macvlan
-
macvlan:
- 容许赋予容器 MAC 地址
-
在该网络里, 容器会被认为是物理设施
none
- 在该策略下, 容器不应用任何网络
-
none 经常用于连贯自定义网络驱动的状况下
其它网络策略模式
-
要想使用其它网络策略模式须要依赖其它第三方插件
数据管理
-
在默认状况下,Docker 所有文件将会存储在容器里的可写的容器层container layer:
- 数据与容器共为一体: 随着容器的隐没, 数据也会隐没. 很难与其它容器程序进行数据共享
- 容器的写入层与宿主机器紧紧耦合: 很难挪动数据到其它容器
- 容器的写入层是通过存储驱动 storage driver 治理文件系统: 存储驱动会应用 Linux 内核的链合文件系统 union filesystem 进行挂载, 相比拟于间接操作宿主机器文件系统的数据卷, 这个额定的形象层会升高性能
-
容器有两种永久化存储形式:
- volumes: 卷
- bind mounts: 绑定挂载
- Linux 中能够应用 tmpfs 进行挂载, windows 用户能够应用 命名管道 named pipe.
-
在容器中, 不论应用哪种永久化存储, 表现形式都是一样的
卷
-
卷: volumes.
- 是宿主机器文件系统的一部分
- 由 Docker 进行治理. 在 Linux 中, 卷存储于 /var/lib/docker/volumes/
- 非 Docker 程序不应该去批改这些文件
- Docker 举荐应用卷进行长久化数据
- 卷能够反对卷驱动 volume drivers: 该驱动容许用户将数据存储到 近程主机 或云服务商 cloud provider或其它
-
没有名字的卷叫作匿名卷anonymous volume. 有名字的卷叫作命名卷named volume. 匿名卷没有明确的名字, 当被初始化时, 会被赋予一个随机名字
绑定挂载
-
绑定挂载: bind mounts
- 通过将宿主机器的门路挂载到容器里的这种形式, 从而实现数据继续化, 因而绑定挂载可将数据存储在宿主机器的文件系统中的任何中央
- 非 Docker 程序能够批改这些文件
- 绑定挂载在 Docker 早起就曾经存在, 与卷存储相比拟, 绑定挂载非常简单明了
- 在开发 Docker 利用时, 应应用命名卷 named volume 代替绑定挂载, 因为用户不能对绑定挂载进行 Docker CLI 命令操作
-
绑定挂载的应用场景:
-
同步配置文件
- 将宿主机的 DNS 配置文件 (/etc/resolv.conf) 同步到容器中
-
在开发程序过程中, 将源代码或者 Artifact 同步至容器中. 这种用法与 Vagrant 相似
tmpfs 挂载
-
-
tmpfs 挂载: tmpfs mounts
- 仅仅存储于内存中, 不操作宿主机器的文件系统. 即不长久化于磁盘
-
用于存储一些非长久化状态, 敏感数据
-
swarm 服务通过 tmpfs 将 secrets 数据 (明码, 密钥, 证书等) 存储到 swarm 服务
命名管道
-
-
命名管道: named pipes
-
通过 pipe 挂载的模式, 使 Docker 主机和容器之间相互通信
-
在容器内运行第三方工具, 并应用命名管道连贯到 Docker Engine API
笼罩问题
-
-
- 当 挂载空的卷 至一个目录中, 目录中你的内容会被复制于卷中, 不会笼罩
-
如果 挂载非空的卷 或绑定挂载 至一个目录中, 那么该目录的内容将会被暗藏 obscured, 当卸载后内容将会复原显示
日志
-
在 Linux 和 Unix 中, 常见的 I / O 流分为三种:
- STDIN: 输出
- STDOUT: 失常输入
- STDERR: 谬误输入
-
默认配置下,Docker 的日志所记录的是命令行的输入后果:
- STDOUT : /dev/stdout
- STDERR : /dev/stderr
-
也能够在宿主主机上查看容器的日志, 应用命令能够查看容器日志的地位:
docker inspect --format='{{.LogPath}}' $INSTANCE_ID
继续集成 -jenkins
jenkins 基本概念
- jenkins 是一个开源的, 提供敌对操作页面的继续集成 (CI) 工具
- jenkins 次要用于继续, 主动的构建或者测试软件我的项目, 监控内部工作的运行
- jenkins 应用 Java 语言编写, 能够在 Tomcat 等风行的 servlet 容器中运行, 也能够独立运行
- 通常与版本管理工具SCM, 构建工具联合应用
- 罕用的版本控制工具有SVN,GIT
-
常见的构建工具有Maven,Ant,Gradle
CI/CD
-
CI: Continuous integration, 继续集成
- 继续集成强调开发人员提交新代码之后,like 进行构建, 单元测试
- 依据测试后果, 能够确定新代码和原有代码是否正确地合并在一起
-
CD: Continuous Delivery, 继续交付
-
在继续集成的根底上, 将集成后的代码部署到更贴近实在运行环境中, 即类生产环境中
- 比方在实现单元测试后, 能够将代码部署到连贯数据库的 Staging 环境中进行更多的测试
-
如果代码没有问题, 能够持续手动部署到生产环境
jenkins 应用配置
-
- 登录 jenkins, 点击新建, 创立一个新的构建工作:
-
跳转到新建界面:
- 工作名称能够自行设定, 但须要全局惟一
- 输出名称后, 抉择构建一个自在格调的软件我的项目
- 点击下方的创立按钮
- 这样就创立了一个构建工作, 而后会跳转到该工作的配置页面
-
在构建工作页面, 能够看到几个选项:
- General
- 源码治理
- 构建触发器
- 构建环境
- 构建
-
构建后操作
General
-
General 用于构建工作的一些根本配置: 名称, 形容等
- 项目名称: 方才创立构建工作设置的名称, 能够在这里进行批改
- 形容: 对构建工作的形容
-
抛弃旧的构建: 服务资源是无限的, 如果保留太多的历史构建, 会导致 jenkins 速度变慢, 并且服务器硬盘资源也会被占满
- 放弃构建天数: 能够自定义, 依据理论状况确定一个正当的值
-
放弃构建的最大个数: 能够自定义, 依据理论状况确定一个正当的值
源码治理
- 源码治理用于配置代码的寄存地位
- Git: 反对支流的 github 和 gitlab 代码仓库
- Repository URL: 仓库地址
- Credentials: 凭证. 能够应用 HTTP 形式的用户名和明码, 也能够是 RSA 文件. 然而要通过前面的 [ADD] 按钮增加凭证
- Branches to build: 构建分支. */master示意 master 分支, 也能够设置为另外的分支
- 源码浏览器: 所应用的代码仓库管理工具, 如 github,gitlab
- URL: 填入上方的仓库地址即可
- Version: gitlab 服务器版本
-
Subversion: 就是 SVN
构建触发器
- 构建工作的触发器
- 触发近程构建(例如, 应用脚本): 这个选项会提供一个接口, 能够用来在代码层面触发构建
- Build after other project are built: 在其它我的项目构建后构建
-
Build periodically: 周期性地构建. 每隔一段时间进行构建
- 日程表: 相似 linux cronttab 书写格局. 下图示意每隔 30 分钟进行一次构建
-
Build when a change is pushed to Gitlab: 罕用的构建触发器, 当有代码 push 到 gitlab 代码仓库时就进行构建
- webhooks: 触发构建的地址, 须要将这个地址配置到 gitlab 中
-
Poll SCM: 这个性能须要与下面的这个性能配合应用. 当代码仓库产生变动时,jekins 并不知道. 这时, 须要配置这个选项, 周期性地查看代码仓库是否产生变动
构建环境
- 日程表: 相似 linux cronttab 书写格局. 下图示意每隔 30 分钟进行一次构建
- 构建环境: 构建之前的筹备工作. 比方指定构建工具, 这里应用 Ant
-
With Ant: 抉择这个选项, 并指定 Ant 版本和 JDK 版本. 须要当时在 jenkins 服务器上安装这两个版本的工具, 并且在 jenkins 全局工具中配置好
构建
- 点击下方的减少构建步骤:
这里有多种减少构建步骤的形式, 在这里介绍 Execute shell 和Invoke Ant - Execute shell: 执行 shell 命令. 该工具是针对 linux 环境的,windows 中对应的工具是 [Execute Windows batch command]. 在构建之前, 须要执行一些命令: 比方压缩包的解压等等
-
Invoke Ant: Ant 是一个 Java 我的项目构建工具, 也能够用来构建 PHP
- Ant Version: 抉择 Ant 版本. 这个 Ant 版本是装置在 jenkins 服务器上的版本, 并且须要在 jenkins[零碎工具]中设置好
- Targets: 须要执行的操作. 一行一个操作工作: 比方上图的 build 是构建,tar是 打包
- Build File: Ant 构建的配置文件. 如果不指定, 默认是在我的项目门路下的 workspace 目录中的build.xml
- properties: 设定一些变量. 这些变量能够在 build.l 中被援用
-
Send files or execute commands over SSH: 发送文件到近程主机或者执行命令脚本
- Name: SSH Server 的名称. SSH Server 能够在 jenkins[零碎设置]中配置
- Source files: 须要发送给近程主机的源文件
- Remove prefix: 移除后面的门路. 如果不设置这个参数, 默认状况下近程主机会主动创立构建源 source file 蕴含的门路
- Romote directory: 近程主机目录
-
Exec command: 在近程主机上执行的命令或者脚本
构建后操作
- 构建后操作: 对构建实现的我的项目实现一些后续操作: 比方生成相应的代码测试报告
- Publish Clover PHP Coverage Report: 公布代码覆盖率的 xml 格局的报告. 门路在 build.xml 中定义
- Publish HTML reports: 公布代码覆盖率的 HTML 报告
- Report Crap: 公布 Crap 报告
-
E-mail Notification: 邮件告诉. 构建实现后发送邮件到指定的邮箱
配置实现后, 点击[保留]其它配置
SSH Server 配置
- 登录 jenkins
- 系统管理
- 零碎设置
-
SSH Servers: jenkins 服务器公钥文件配置好之后新增 SSH Server 只须要配置这一个选项即可
- name: 服务名称. 自定义, 须要全局惟一
- HostName: 主机名. 间接应用 IP 地址即可
- Username: 新增 Server 的用户名, 这里配置的是 root
-
Remote Directory: 近程目录. jenkins 服务器发送文件给新增的 server 时默认在这个目录
Ant 配置文件 – build.xml
- Ant 构建配置文件build.xml :
- project name: 项目名称. 和 jenkins 所构建的项目名称对应
-
target name=”build”: 构建的名称. 和 jekins 构建步骤中的 targets 对应.
- depends: 指明构建须要进行的一些操作
- property: 用来设置变量
-
fileset: 指明一个文件夹
- include: 指明须要蕴含的文件
- exclude: 指明不须要蕴含的文件
- tar: 打包这个文件夹匹配到的文件
-
target: 理论的操作步骤:
- make_runtime: 创立一些目录
- phpcs: 利用 PHP_CodeSniffer 工具对 PHP 代码标准与质量检查工具
-
target name=”tar”: 打包文件
- 因为 build 中没有蕴含这个 target. 所以默认状况下, 执行 build 是不会打包文件的
- 所以在 jenkins 配置界面中 Ant 构建步骤中的 [targets], 才会有[build] 和[tar]这两个 targets
-
如果 build.xml 中 build 这个 target depends 中曾经蕴含 tar, 就不须要在 jenkins 中减少tar 了
配置 Gitlab webhooks
- 在 gitlab 的 project 页面关上settings
- 关上web hooks
- 点击[ADD WEB HOOK] 来增加 webhook
- 将之前的 jenkins 配置中的 url 增加到这里
-
增加实现后, 点击 [TEST HOOK] 进行测试, 如果显示 SUCCESS 则示意增加胜利
配置 phpunit.xml
- phpunit.xml: 是 phpunit 工具用来单元测试所须要的配置文件
- 这个文件的名称是能够自定义的, 只有在 build.xml 中配置好名字即可
- 默认状况下, 如果应用 phpunit.xml, 就不须要在build.xml 中配置文件名
-
fileset dir: 指定单元测试文件所在门路.
- include: 指定蕴含哪些文件, 反对通配符
-
exclude: 指定不蕴含的文件
构建 jenkins project
- 第一次配置好 jenkins project 后, 会触发一次构建
- 尔后, 每当有 commit 提交到 master 分支(依据配置中的分支触发), 就会触发一次构建
-
也能够在 project 页面手动触发构建: 点击 [立刻构建] 即可手动触发构建
构建后果阐明
构建状态
- Successful: 蓝色. 构建实现, 并且是稳固的
- Unstable: 黄色. 构建实现, 然而是不稳固的
- Failed: 红色. 构建失败
-
Disable: 灰色. 构建已禁用
构建稳定性
-
构建稳定性用天气示意: 天气越好示意构建越稳固
- 晴
- 晴转多云
- 多云
- 小雨
-
雷阵雨
构建历史界面
-
console output: 输入构建的日志信息
jenkins 权限治理
- jenkins 中默认的权限管理体系不反对用户组和角色配置, 因而须要装置第三方插件来反对角色的配置
-
应用 Role Strategy Plugin 进行权限治理:
- 我的项目视图:
- 装置 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 结尾的我的项目
- 设置后的成果如图:
自动化测试 -TestNG
TestNG 基本概念
- 我的项目视图:
- TestNG是一个 Java 语言的开源测试框架, 相似 JUnit 和 NUnit, 然而功能强大, 更易于应用
-
TestNG的设计指标是为了笼罩更宽泛的测试类别范畴:
- 单元测试
- 功能测试
- 端到端测试
- 集成测试
-
TestNG 的次要性能:
- 反对注解
- 反对参数化和数据驱动测试: 应用 @DataProvider 或者 XML 配置
- 反对同一类的多个实例: @Factory
-
灵便的执行模式:
- TestNG的运行, 既能够通过 Ant 的build.xml: 有或这没有一个测试套定义. 又能够通过带有可视化成果的 IDE 插件
- 不须要 TestSuite 类, 测试包, 测试组以及抉择运行的测试. 都通过 XML 文件来定义和配置
-
并发测试:
- 测试能够运行在任意大的线程池中, 并有多种运行策略能够抉择: 所有办法都有本人的线程, 或者每一个测试类一个线程等等
- 测试代码是否线程平安
- 嵌入 BeanShell 能够取得更大的灵活性
- 默认应用 JDK 运行和相干日志性能, 不须要额定减少依赖
- 应用服务器测试的依赖办法
-
分布式测试: 容许在从机上进行分布式测试
TestNG 环境配置
- 配置好主机的 Java 环境, 应用命令 java -version 查看
- 在 TestNG 官网, 下载 TestNG 对应零碎下的 jar 文件
- 零碎环境变量中增加指向 jar 文件的门路
-
在 IDEA 中装置 TestNG
TestNG 的根本用法
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"); } }
TestNG 的根本注解
注解 形容 @BeforeSuit 注解办法只运行一次, 在此套件中所有测试之前运行 @AfterSuite 注解办法只运行一次, 在此套件中所有测试之后运行 @BeforeClass 注解办法只运行一次, 在以后类中所有办法调用之前运行 @AfterClass 注解办法只运行一次, 在以后类中所有办法调用之后运行 @BeforeTest 只运行一次, 在所有的测试方法执行之前运行 @AfterTest 只运行一次, 在所有的测试方法执行之后运行 @BeforeGroups 组的列表, 配置办法之前运行.
此办法是保障在运行属于任何这些组的第一个测试, 该办法将被调用@AfterGroups 组的名单, 配置办法之后运行.
此办法是保障运行属于任何这些组的最初一个测试后不久,, 该办法将被调用@BeforeMethod 在每一个 @test 测试方法运行之前运行
比方: 在执行完测试用例后要重置数据能力执行第二条测试用例时, 能够应用这种注解形式@AfterMethod 在每一个 @test 测试方法运行之后运行 @DataProvider 标记一个办法, 提供数据的一个测试方法
注解的办法必须返回一个 Object[][], 其中每个对象的 [] 的测试方法的参数列表能够调配
如果有 @Test 办法, 想要应用从这个 DataProvider 中接管的数据, 须要应用一个 dataProvider 名称等于这个注解的名称@Factory 作为一个工厂, 返回 TestNG 的测试类对象中被用于标记的办法
该办法必须返回 Object[]@Listeners 定义一个测试类的监听器 @Parameters 定义如何将参数传递给 @Test 办法 @Test 标记一个类或者办法作为测试的一部分 testng.xml
属性 形容 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>
-
在 suite 中, 同时应用 parallel 和thread-count:
- parallel: 指定并行测试范畴 tests,methods,classes
- thread-count: 并行线程数
- preserve-order: 当设置为 true 时, 节点下的办法按程序执行
- verbose: 示意记录日志的级别, 在 0 – 10 之间取值
-
< parameter name=”userName”, value=”15952031403″ > : 给测试代码传递键值对参数, 在测试类中通过注解 @Parameter({“userName”}) 获取
参数化测试
- 当测试逻辑一样, 只是参数不一样时, 能够采纳数据驱动测试机制, 防止反复代码
- TestNG通过 @DataProvider实现数据驱动
-
应用 @DataProvider 做数据驱动:
- 数据源文件能够是 EXCEL,XML, 甚至能够是 TXT 文本
-
比方读取 xml 文件:
- 通过 @DataProvider 读取 XML 文件中的数据
- 而后测试方法只有标示获取数据起源的 DataProvider
-
对应的 DataProvider 就会将读取的数据传递给该 test 办法
构建 XML 数据文件
<?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>
读取 XML 文件
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; } }
DataProvider 类
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; } } }
在 test 办法中援用 DataProvider
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")); } }
-
xml 中的父节点与 test 的办法名对应:
- xml 中同名父节点的个数就意味着该 test 办法会被反复执行多少次
-
当 DataProvider 与 test 办法不在同一个类时, 须要指明 DataProvider 类:
-
@Test(dataProvider=”dataProvider”, dataProviderClass= GenerateData.class)
TestNG 重写监听类
-
-
TestNG会监听每个测试用例的运行后果. 能够应用监听定制一些自定义的性能, 比方主动截图, 发送数据给服务器:
- 新建一个继承自 TestListenerAdapter 的类
-
重写实现后, 在 test 办法前增加 @Listener(TestNGListener.class) 注解
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(); } } } }