MyBatis Plus是在 MyBatis 的基础上只做增强不做改变,可以简化开发,提高效率.
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;
<dependency>
<groupId>com.baomidougroupId>
<artifactId>mybatisplus-spring-boot-starterartifactId>
<version>1.0.5version>
dependency>
<dependency>
<groupId>com.baomidougroupId>
<artifactId>mybatis-plusartifactId>
<version>2.1.9version>
dependency>
<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">
<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"/>
<property name="removeAbandoned" value="true"/>
<property name="removeAbandonedTimeout" value="1800"/>
<property name="logAbandoned" value="true"/>
<property name="filters" value="mergeStat"/>
bean>
<bean id="transactionManager"
class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource" />
bean>
<tx:annotation-driven transaction-manager="transactionManager"/>
<bean id="sqlSessionFactory" class="com.baomidou.mybatisplus.spring.MybatisSqlSessionFactoryBean">
<property name="dataSource" ref="dataSource"/>
<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"/>
<property name="globalConfig" ref="globalConfig"/>
bean>
<bean id="globalConfig" class="com.baomidou.mybatisplus.entity.GlobalConfiguration">
<property name="idType" value="3" />
bean>
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<property name="basePackage" value="cn.lqdev.learning.springboot.chapter9.biz.dao"/>
bean>
beans>
@Configuration
@ImportResource(locations = {
"classpath:/mybatis/spring-mybatis.xml"})
//@MapperScan("cn.lqdev.learning.springboot.chapter9.biz.dao")
//@EnableTransactionManagement
public class MybatisPlusConfig {
}
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;
<dependencies>
<dependency>
<groupId>com.baomidougroupId>
<artifactId>mybatis-plusartifactId>
<version>2.3version>
dependency>
<dependency>
<groupId>junitgroupId>
<artifactId>junitartifactId>
<version>4.12version>
dependency>
<dependency>
<groupId>com.alibabagroupId>
<artifactId>druidartifactId>
<version>1.1.10version>
dependency>
<dependency>
<groupId>mysqlgroupId>
<artifactId>mysql-connector-javaartifactId>
<version>5.1.39version>
dependency>
<dependency>
<groupId>org.springframeworkgroupId>
<artifactId>spring-contextartifactId>
<version>4.3.9.RELEASEversion>
dependency>
<dependency>
<groupId>org.springframeworkgroupId>
<artifactId>spring-ormartifactId>
<version>4.3.9.RELEASEversion>
dependency>
dependencies>
<configuration />
jdbc.url=jdbc:mysql://localhost:3306/mp
jdbc.username=mp
jdbc.password=mp
<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>
<bean id="sqlSessionFactoryBean"
class="com.baomidou.mybatisplus.spring.MybatisSqlSessionFactoryBean">
<property name="dataSource" ref="dataSource">property>
<property name="configLocation" value="classpath:mybatis-config.xml">property>
<property name="typeAliasesPackage" value="com.jas.bean">property>
<property name="globalConfig" ref="globalConfiguration">property>
<property name="plugins">
<list>
<bean class="com.baomidou.mybatisplus.plugins.PaginationInterceptor" />
<bean class="com.baomidou.mybatisplus.plugins.PerformanceInterceptor">
<property name="maxTime" value="1000" />
<property name="format" value="true" />
bean>
list>
property>
bean>
<property name="dbColumnUnderline" value="true">property>
<property name="idType" value="0">property>
<property name="tablePrefix" value="tbl_">property>
bean>
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<property name="basePackage" value="com.jas.mapper">property>
bean>
@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() 方法
/**
* 不定义任何接口方法
*/
public interface EmployeeMapper extends BaseMapper<Employee> {
}
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接口获得通用的的增删改查方法,复杂的SQL也可以使用条件构造器拼接.不过复杂的业务需求还是要编写SQL语句的,流程和MyBatis一样.
<dependency>
<groupId>org.apache.velocitygroupId>
<artifactId>velocity-engine-coreartifactId>
<version>2.0version>
<scope>testscope>
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";
/**
*
* MySQL 生成演示
*
*/
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)document
.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时写法为:
//这个可以修改的,设置字段的@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());
}
}
条件构造器主要提供了实体包装器,用于处理SQL语句拼接,排序,实体参数查询:使用的是数据库字段,不是Java属性
@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
/**
*
* @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结束");
}
/**
*
* @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 | 拼接在最后 |
在多表关联时,条件构造器和通用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新增一个节点:
<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文件:
<bean id="sqlSessionFactory" class="com.baomidou.mybatisplus.spring.MybatisSqlSessionFactoryBean">
<property name="dataSource" ref="dataSource"/>
<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"/>
<property name="globalConfig" ref="globalConfig"/>
<property name="vfs" value="com.baomidou.mybatisplus.spring.boot.starter.SpringBootVFS">property>
bean>
mybatis的插件机制使用只需要注册即可
<plugins>
<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">
<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 |
通过kafka消费,就是kafkaFireHose.
同时,实时节点的另外一个模块Plumer,用于Segment的生成,并且按照指定的周期,
将本周期内生成的所有数据块合并成一个
1.实时节点生产出Segment文件,并且存到文件系统中
2.Segment文件的存放到Mysql等其他外部数据库中
3.Master通过Mysql中的MetaStore,通过一定的规则,将Segment分配给属于它的节点
4.历史节点得到Master发送的指令后会从文件系统中拉取属于自己的Segment文件,并且通过zookeeper,告知集群,自己提供了此块Segment的查询服务
5.实时节点丢弃Segment文件,并且声明不在提供此块文件的查询服务
除了通过实时节点生产Segment文件之外,druid还提供了一组索引服务来摄入数据
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,启动在具体节点上具体任务的执行
1.Zuul的过滤器之间没有直接的相互通信,他们之间通过一个RequestContext的静态类来进行数据传递的。RequestContext类中有ThreadLocal变量来记录每个Request所需要传递的数据
2.Zuul的过滤器是由Groovy写成,这些过滤器文件被放在Zuul Server上的特定目录下面,Zuul会定期轮询这些目录,修改过的过滤器会动态的加载到Zuul Server中以便过滤请求使用
Zuul可以通过加载动态过滤机制实现Zuul的功能:
1.首先根据Type获取所有输入该Type的filter:List list
2.遍历该list,执行每个filter的处理逻辑:processZuulFilter(ZuulFilter filter)
3.RequestContext对每个filter的执行状况进行记录,应该留意,此处的执行状态主要包括其执行时间、以及执行成功或者失败,如果执行失败则对异常封装后抛出
4.到目前为止,Zuul框架对每个filter的执行结果都没有太多的处理,它没有把上一filter的执行结果交由下一个将要执行的filter,仅仅是记录执行状态,如果执行失败抛出异常并终止执行
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-data-redisartifactId>
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(有序集合)
@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(有序集合)
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.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 > 子标签,用来定义回滚语句:
<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:
<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.liquibasegroupId> <artifactId>liquibase-maven-pluginartifactId> <version>3.4.2version> <configuration> <propertyFile>src/main/resources/liquibase.propertiespropertyFile> <propertyFileWillOverride>truepropertyFileWillOverride> <outputChangeLogFile>src/main/resources/changelog_dev.xmloutputChangeLogFile> 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=指定前缀
列名 | 类型 | 是否为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 |
- 确保版本号唯一 ,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.flywaydbgroupId>
<artifactId>flyway-coreartifactId>
<version>5.0.3version>
dependency>
<plugin>
<groupId>org.flywaydbgroupId>
<artifactId>flyway-maven-pluginartifactId>
<version>5.0.3version>
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 | 迁移时是否校验 |
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
- 项目视图:
- 安装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类 |
<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>
<data>
<login>
<username>user1username>
<password>123456password>
login>
<login>
<username>user2username>
<password>345678password>
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();
}
}
}
}