groovy简单入门
使用闭包
Groovy闭包是一种可执行代码块的方法,闭包也是对象,可以像方法一样传递参数。
Groovy中的闭包完全避免了代码的冗长,而且可以辅助创建轻量级、可复用的代码片段。
以下例子就是使用闭包去计算偶数和,偶数积,偶数的平方。
def pickEven(n, block) {
for (int i = 2; i <= n; i += 2) {
block(i)
}
}
total = 0
pickEven(10, { total += it })
println "1 - 10 的偶数的和 $total"
prod = 1
pickEven(10, { prod *= it })
println "1 - 10 的偶数的积 $prod"
squared = []
pickEven(10, { squared << it**2 })
println "1 - n 的偶数的平方所组成的集合 $squared"
输出
Groovy 基础语法
变量
变量的类型
Groovy中的基本类型最终会被编译器包装成对象类型。
int x = 10
println x.class
double y = 3.14
println y.class
输出
变量的定义
强类型定义及定义的时候写明变量的类型,而def则由编译器自行推导变量的类型。
def x1 = 10
def y1 = 3.14
def str = 'groovy study'
println x1.class
println y1.class
println str.class
输出
强类型定义方式和弱类型def定义方式的选择
变量就是应用于自己的类或者自己的模块而不会应用于其它类或者其他模块,推荐使用def类型,这样可以随时动态的转换为其它类型。
变量要用于其它类或是其它模块,强烈建议使用强类型定义方式。使用强类型定义的方式不能动态转换类型,才能使外界传入或者调用的时候不会对于数据的类型产生疑惑,这样就保证外界传入的数据一定是我们想要的正确的类型的数据。
def x1 = 10
println x1.class
x1 = "dynamic type conversion"
println x1.class
输出
字符串
String
和java中的String一样
GString
单引号定义
单引号定义的就是java中的String,内容即为 ‘’ 内的字符串,并且不可更改。
def str = 'a single string'
//有特殊字符同样的通过反斜杠转义
def str1 = 'a single \'special\' string'
三个单引号定义
三个单引号定义的是有格式的字符串,会直接按照我们写的格式进行输出,而不用像java中进行拼接。
def trebleStr = '''line one
line two
line three '''
def trebleStr2 = '''
line one
line two
line three '''
def trebleStr3 = '''\
line one
line two
line three '''
println trebleStr
println trebleStr2
println trebleStr3
输出
- trebleStr2比trebleStr多了一行空格,trebleStr3与trebleStr结果相同
双引号定义
可扩展的字符串
def name = "Groovy"
println name.class
def sayHello = "Hello $name"
println sayHello
println sayHello.class
//扩展内容还可以是表达式
def sum = "the sum of 2 and 3 equals ${2 + 3}"
println sum
输出
Groovy中常用的String方法
字符串填充
center(Number numberOfChars,CharSequence padding) ,将字符串作为中心进行填充。
当numberOfChars小于或等于str本身的长度时,不进行填充操作,大于则用pandding扩展至长度numberOfChars,从字符串的右边(尾)进行填充,再到左边(头)
def str = "groovy"
println str.center(5,"a")
println str.center(6,"a")
println str.center(7,"a")
println str.center(8,"a")
println str.center(9,"a")
println str.center(8)
输出
padLeft(Number numberOfChars,CharSequence padding) ,在字符串的左边进行填充。
当numberOfChars小于或等于str本身的长度时,不进行填充操作,大于则用pandding扩展至长度numberOfChars,从字符串的左边进行填充
str = "groovy"
println str.padLeft(6,"a")
println str.padLeft(7,"a")
println str.padLeft(10,"a")
输出
padRight(Number numberOfChars,CharSequence padding),在字符串的右边进行填充。
当numberOfChars小于或等于str本身的长度时,不进行填充操作,大于则用pandding扩展至长度numberOfChars,从字符串的右边进行填充
字符串比较
def str = "groovy"
def str2 = "Groovy"
println str.compareTo(str2)
// 忽略大小写
println str.compareToIgnoreCase(str2)
println str2.compareTo(str)
//可用操作符直接进行比较
println str > str2
println str == str2.toLowerCase()
输出
获取字符串中的字符
def str = "groovy"
println str.getAt(0)
println str.getAt(0..1)
println str[0]
println str[0..1]
输出
字符串中的减法(取差集)
def str = "groovy"
def str2 = "hello"
def str3 = "hello groovy"
def str4 = "groovy hello "
println str.minus(str2)
println str.minus(str3)
println str3.minus(str2)
println str3.minus(str4)
println str3 - str
输出
其它
其它还有很多方法用到时看看即可。例子中只演示了部分。
def str = "hello groovy"
// 字符串反转
println str.reverse()
// 首字母大写
println str.capitalize()
// 是否全是数字
println str.isNumber()
def str2 = "1234"
println str2.toInteger()
println str2.toBigDecimal()
println str2.toDouble()
输出
for循环
Java中的循环
String message = ''
for (int i = 0; i < 5; i++) {
message += 'Hi '
}
使用 in 关键字
使用 .. 方式. 在某一范围内()
def x = 0
for ( i in 0..9 ) {
x += i
}
println(x)
循环遍历list集合
def x = 0
for ( i in [0, 1, 2, 3, 4] ) {
x += i
}
println(x)
遍历数组
def array = (0..4).toArray()
def x = 0
for ( i in array ) {
x += i
}
println(x)
遍历map
def map = ['abc':1, 'def':2, 'xyz':3]
def x = 0
for ( e in map ) {
x += e.value
}
println(x)
遍历map中的value
def map = ['abc':1, 'def':2, 'xyz':3]
def x = 0
for ( v in map.values() ) {
x += v
}
println(x)
遍历字符串中的字符
def text = "abc"
def list = []
for (c in text) {
list.add(c)
}
println(list)
Grovvy sql
数据库设计
member_info表设计
CREATE TABLE `member_info` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`name` varchar(20) COLLATE utf8_unicode_ci NOT NULL COMMENT '姓名',
`position` varchar(50) COLLATE utf8_unicode_ci NOT NULL COMMENT '职位',
`entry_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '入职时间',
PRIMARY KEY (`id`) USING BTREE
)ENGINE = InnoDB
CHARACTER SET = utf8mb4
COLLATE = utf8mb4_unicode_ci COMMENT ='员工信息表';
member_info表中的数据
连接数据库
用groovy.sql.Sql的newInstance( )连接数据库
public static Sql newInstance(String url, String user, String password, String driverClassName) throws SQLException, ClassNotFoundException {
loadDriver(driverClassName);
return newInstance(url, user, password);
}
例子:连接本地的test数据库,用户名为 root ,密码为 root ,数据库类型为mysql
import groovy.sql.Sql
url='jdbc:mysql://localhost:3306/test?useUnicode=true&characterEncoding=utf8'
driver='com.mysql.jdbc.Driver'
username='root'
passwd='root'
//创建sql实例
def sql = Sql.newInstance(url, username, passwd, driver)
简单查询
eachRow
eachRow均不返回任何内容,如果想操作查询的数据,则应该在闭包中对每一行进行操作。
(it表示默认的当前数据)
sql.eachRow("select * from `member_info`") {
println it[0] //可以按表字段从左到右依次下标访问
println it.name //直接按字段名访问
}
// 不使用 it
sql.eachRow("select * from `member_info`") {member->
println member[0]
println member.name
}
输出结果
rows
rows返回行列表
List rows = sql.rows("select * from `member_info`")
println rows[0].name // 输出第一条数据的字段名为 ‘name’ 的数据
输出
firstRow
取第一行数据
def memberOne=sql.firstRow("select * from `member_info`")
println memberOne
输出
传递参数
增删改查传递参数的方式都是一样的
以查询 eachRow为例
eachRow(Map params, String sql)
sql.eachRow(['id':1],"select * from `member_info`where id =:id"){
println it.id
println it.name
}
输出
eachRow(String sql, List params)
sql.eachRow("select * from `member_info`where id =? and name=?",[2,'唐圆圆']){
println it.id
println it.name
}
输出
eachRow(String sql, Map params)
sql.eachRow("select * from `member_info`where id =:id",['id': 1]){
println it.id
}
输出
eachRow(GString gstring)
def id = 1
sql.eachRow("select * from `member_info`where id =$id") {
println it
}
输出
分页查询
def page = 2 //页码,从1开始
def maxRows = 1 //每页数据量
def offset = maxRows * (page - 1) + 1 //生成offset
rows = sql.rows("select * from `member_info`", offset, maxRows)
rows.each {
println it.id
println it.name
println it.entry_time
}
输出
查询指定字段
MemberInfoDTO.class
class MemberInfoDTO {
def name
def position
}
查询语句
MemberInfoDTO memberInfo = sql.firstRow("select name ,position from member_info")
println "name = " + memberInfo.name + " ; position = " + memberInfo.position
输出
插入
def name="张啾啾"
def position="BD"
def entry_time= LocalDateTime.now()
sql.execute("insert into member_info (name, position,entry_time) values (${name}, ${position},${entry_time})")
更新
name = "张啾啾"
position = "城市经理"
sql.executeUpdate("update member_info set position = ? where name=?", [position, name])
删除
sql.execute(['id': 4], "delete from member_info where id =:id")
事务withTransaction
sql.withTransaction的参数是一个闭包,闭包中的sql操作就是在事务中进行的,如果有一个操作失败,则所有的操作都会回滚。
def name=["张嘉极","刘大伟"]
def position=["BD","城市经理"]
sql.withTransaction {
sql.execute "insert into member_info (name, position,entry_time) values (${name[0]}, ${position[0]},${LocalDateTime.now()})"
sql.execute "insert into member_info (name, position,entry_time) values (${name[1]}, ${position[1]},${LocalDateTime.now()})"
}
批量执行withBatch
sql.withBatch 有两个参数,第二个参数为要执行操作的闭包,第一个整形的参数,表示每次的执行量。例如代码中的3,就表示调用3次addBatch方法之后,就会执行一次插入操作。
sql.withBatch(3) { stmt ->
stmt.addBatch("INSERT INTO member_info (name, position,entry_time) VALUES ('张家辉', 'BD' , '2020-10-12 11:03:40')")
stmt.addBatch "insert into member_info (name, position,entry_time) values ('邓不利', 'BD' , '2020-10-12 11:03:40');"
stmt.addBatch "insert into member_info (name, position,entry_time) values ('多老板', 'BD' , '2020-10-12 11:03:40'));"
stmt.addBatch "insert into member_info (name, position,entry_time) values ('特朗新', 'BD' , '2020-10-12 11:03:40'));"
stmt.addBatch "insert into member_info (name, position,entry_time) values ('多富贵', 'BD' , '2020-10-12 11:03:40'));"
stmt.addBatch "insert into member_info (name, position,entry_time) values ('钱多多', 'BD' , '2020-10-12 11:03:40'));"
}
注意:
- 任何的SQL语句组合都可以加到批量中,并不需要是插入同张表。
- 为避免SQL注入,强烈建议使用预编译语句prepared statment,可以通过witchBatch同态方法,使用GString和参数列表作为参数。
def name = ["邓不利", "多老板", "特朗新", "钱多多", "多来钱", "多富贵"]
def position = ["BD", "城市经理"]
def qry = "INSERT INTO member_info (name, position,entry_time) VALUES (?, ? ,?);"
sql.withBatch(3, qry) { ps ->
ps.addBatch(name[0], position[0],LocalDateTime.now())
ps.addBatch(name[1], position[1],LocalDateTime.now())
ps.addBatch(name[2], position[0],LocalDateTime.now())
ps.addBatch(name[3], position[1],LocalDateTime.now())
ps.addBatch(name[4], position[0],LocalDateTime.now())
ps.addBatch(name[5], position[1],LocalDateTime.now())
}
基于Groovy的Spock框架
Maven依赖
使用Spock框架,需要添加Maven依赖
<dependency>
<groupId>org.spockframework</groupId>
<artifactId>spock-core</artifactId>
<version>1.1-groovy-2.4</version>
<scope>compile</scope>
</dependency>
被测试的类
//数据库映射类
public class DbEntity {
long id;
String requestNo;
//省略get set
}
//被测试的service
@Component
public class MyService {
@Autowired
private DbEntityDao dbEntityDao;
private String value;
public String getValue() {
return value;
}
public int stringToInteger(String valueStr) {
return Integer.valueOf(valueStr);
}
public String getUtilValue(String id) {
return id+MyServiceUtil.getValue(id);
}
public void setValue(String value) {
this.value = value;
}
public long selectByRequestNo(String requestNo){
DbEntity dbEntity = dbEntityDao.selectByRequestNo(requestNo);
return dbEntity.getId();
}
public int add(int a,int b){
return a+b;
}
}
// 假装查询数据库操作类
public class DbEntityRepositoryImpl implements DbEntityRepository {
@Override
public DbEntity selectByRequestNo(String requestNo) {
DbEntity dbEntity = new DbEntity();
dbEntity.setRequestNo("123");
dbEntity.setId(456);
return dbEntity;
}
}
//工具类
public class MyServiceUtil {
public static String getValue(String value){
return "MyServiceUtil";
}
}
given-when-then声明
given-when-then是Spock的基本句式,使单元测试层次清晰。
此外还有and关键字,用于将大段的声明分割开来。
所有的Spock单元测试都继承spock.lang.Specification,Specification基于Groovy dsl提供了测试环境。
Spock支持将一个句子作为方法名,实现自解释。
Stub用来创建要模拟的测对象。
“>>”用来表示模拟对象的返回值。
“>>>”用来表示同一方法多次按顺序调用返回不同值。
下划线“_”表示匹配所有的输入值。
Spock不使用Assert来校验结果,then声明后面的表达式result == “1”就相当于Junit中的Assert.assertTrue(result.equals(“1”))
def "最基本的测试:mock返回值"() {
given: "given用来准备mock对象,可以放到when里面"
and: "and声明可选"
MyService myService = Stub(MyService)
myService.getValue() >> "1"
when: "when里面调用被测试的方法"
String result = myService.getValue()
then: "then用来验证结果"
result == "1"
}
def "最基本的测试:多次调用返回不同值"() {
given: "返回多个值使用三个>"
MyService myService = Stub(MyService)
myService.getValue() >>> ["1", "2", "3"]
//except相当于when then的合并
expect:
myService.getValue() == "1"
myService.getValue() == "2"
myService.getValue() == "3"
}
def "测试模糊匹配"() {
given:
MyService myService = Stub(MyService)
//下划线表示匹配所有输入值
myService.stringToInteger(_) >> 999
//except相当于when then的合并
expect: "有时候我们并不在乎输入值"
myService.stringToInteger("1") == 999
myService.stringToInteger("2") == 999
myService.stringToInteger("3") == 999
}
校验模拟对象的行为
Stub只能模拟对象的返回值,而Mock更进一步,不仅能模拟对象,还能校验对象的调用次数等行为。
表达式: N * mockedObject.method(arguments)>>value,表示参数为arguments的method方法调用N次,返回值是value。该表达式一般放在then后面。
注意,尽管then声明放在when后面,由于基于Groovy AST语法解析树,Spock会先解析该表达式,然后在when后面的测试类执行后再进入then后面的校验逻辑。
def "校验mock对象的调用次数"() {
given:
DbEntity expectEntity = new DbEntity()
//groovy的with语法,简化创建对象
expectEntity.with {
id = 456
requestNo = mockRequestNo
}
//mock跟Stub不同的是可以校验mock对象的调用次数
DbEntityDao dao = Mock(DbEntityDao)
MyService myService = new MyService()
myService.dbEntityDao = dao
when:
long id = myService.selectByRequestNo(mockRequestNo)
then:
id == 456
//then除了验证结果,还可以设置mock对象返回值,
//Spock会先解析then中定义的mock,等when执行后再校验mock行为
1 * dao.selectByRequestNo(mockRequestNo) >> expectEntity
}
def "抓取输入值"(){
given:
def resultCapture = null
DbEntityDao dao = Stub(DbEntityDao.class)
//定义lambda表达式,抓取输入值
dao.selectByRequestNo({v-> resultCapture = v })>>null
when:
dao.selectByRequestNo("123")
then:
resultCapture == "123"
}
参数化测试
Spock的参数化测试比Junit更加简洁。
直接使用表格形式来定义输入值跟期望值。
注意输入的参数名必须跟被测试方法参数名一致。
def "test Parameterized"() {
when:
MyService myService = new MyService()
then:
myService.add(a, b) == addResult
where: "准备参数,输入参数名必须跟方法里面的参数名一致"
a | b || addResult
1 | 1 || 2
1 | 2 || 3
2 | 2 || 4
}