前言:
数据库准备
在学习Mybatis之前我们需要安装MySql和Navicat,去官网下载即可。
下载好之后,先用navicat与数据库建立连接
输入你的数据库的用户名和密码,这里的连接名随便取。
新建查询,运行sql语句创建数据库
创建完之后记得关闭连接,重新连接刷新一下。
创建表
-- 创建表[⽤⼾表]
DROP TABLE IF EXISTS user_info;
CREATE TABLE `user_info` (
`id` INT ( 11 ) NOT NULL AUTO_INCREMENT,
`username` VARCHAR ( 127 ) NOT NULL,
`password` VARCHAR ( 127 ) NOT NULL,
`age` TINYINT ( 4 ) NOT NULL,
`gender` TINYINT ( 4 ) DEFAULT '0' COMMENT '1-男 2-⼥ 0-默认',
`phone` VARCHAR ( 15 ) DEFAULT NULL,
`delete_flag` TINYINT ( 4 ) DEFAULT 0 COMMENT '0-正常, 1-删除',
`create_time` DATETIME DEFAULT now(),
`update_time` DATETIME DEFAULT now() ON UPDATE now(),
PRIMARY KEY ( `id` )
) ENGINE = INNODB DEFAULT CHARSET = utf8mb4;
-- 添加⽤⼾信息
INSERT INTO mybatis_test.user_info( username, `password`, age, gender, phone )
VALUES ( 'admin', 'admin', 18, 1, '18612340001' );
INSERT INTO mybatis_test.user_info( username, `password`, age, gender, phone )
VALUES ( 'zhangsan', 'zhangsan', 18, 1, '18612340002' );
INSERT INTO mybatis_test.user_info( username, `password`, age, gender, phone )
VALUES ( 'lisi', 'lisi', 18, 1, '18612340003' );
INSERT INTO mybatis_test.user_info( username, `password`, age, gender, phone )
VALUES ( 'wangwu', 'wangwu', 18, 1, '18612340004' );
输入sql查询语句验证一下有没有查询成功
Java对象准备
数据库这边已经处理好了,现在我们要创建Java对象来和数据库进行映射。
创建项目的时候添加以下依赖
import lombok.Data;
import java.util.Date;
@Data
public class UserInfo {
private Integer id;
private String username;
private String password;
private Integer age;
private Integer gender;
private String phone;
private Integer deleteFlag;
private Date createTime;
private Date updateTime;
}
实现mapper接口
import com.example.mybatistest.demos.model.UserInfo;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Select;
import java.util.List;
@Mapper
public interface UserInfoMapper {
@Select("SELECT * FROM user_info")
List<UserInfo> selectAll();
}
🆗到这里执行的代码已经结束了,接下来我们写测试的代码。
写一个接口来测试代码
import com.example.mybatistest.demos.mapper.UserInfoMapper;
import com.example.mybatistest.demos.model.UserInfo;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.List;
@RestController
public class UserController {
@Autowired
UserInfoMapper userInfoMapper;
@RequestMapping("/select")
public List<UserInfo> select(){
return userInfoMapper.selectAll();
}
}
别忘了配置数据库的连接。其中mybatis_test就是我们上面创建数据库的名称,如果你是跟着我做的就不需要修改。下面是目前项目的架构,修改配置文件的位置在application.properties文件中。
spring.datasource.url=jdbc:mysql://127.0.0.1:3306/mybatis_test?characterEncoding=utf8&useSSL=false
spring.datasource.username=数据库账户
spring.datasource.password=数据库密码
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
启动项目之后访问。
上面是使用写一个测试的接口来验证,下面我们使用单元测试的方法来验证。
首先我们在要生成测试代码的地方右键然后选择Generate,然后选择test
单元测试的代码
其中@SpringBootTest注解是启动spring容器的注解,@BeforeEach是在每一个测试方法之前执行,@AfterEach是在每一个测试方法之后执行,@Test是测试方法
import com.example.mybatistest.demos.model.UserInfo;
import lombok.extern.slf4j.Slf4j;
import org.apache.ibatis.annotations.Mapper;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import java.util.List;
import static org.junit.jupiter.api.Assertions.*;
@SpringBootTest
@Mapper
@Slf4j
class UserInfoMapperTest {
@Autowired
private UserInfoMapper userInfoMapper;
@BeforeEach
void setUp(){
log.info("setup");
}
@AfterEach
void tearDown(){
log.info("After");
}
@Test
void selectAll() {
List<UserInfo> list = userInfoMapper.selectAll();
log.info(list.toString());
}
}
结果:
常见的错误:
(1).没有配置数据库相关信息
(2).数据库账户或者密码错误
(3).数据库错误
(4).该表在数据库中不存在
(5).该字段在数据库中的表中错误或者不存在
1.什么是Mybatis
Mybatis是一个持久层(指的就是持久化操作的层,通常指的是数据访问层dao,是用来操作数据库的)的框架,用于简化JDBC的开发。
2.配置Mybatis相关日志
#配置打印 MyBatis日志
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
mybatis.configuration.log-impl=org.apache.ibatis.logging.stdout.StdOutImpl
再次运行结果:
3.使用注解来写Mybatis
传递参数
@Mapper
public interface UserInfoMapper {
@Select("SELECT * FROM user_info")
List<UserInfo> selectAll();
@Select("SELECT * FROM user_info where id =#{id}")
UserInfo selectOne(Integer id);
}
测试代码:
import com.example.mybatistest.demos.model.UserInfo;
import lombok.extern.slf4j.Slf4j;
import org.apache.ibatis.annotations.Mapper;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import java.util.List;
@SpringBootTest
@Mapper
@Slf4j
class UserInfoMapperTest {
@Autowired
private UserInfoMapper userInfoMapper;
@Test
void selectAll() {
List<UserInfo> list = userInfoMapper.selectAll();
log.info(list.toString());
}
@Test
void selectOne() {
UserInfo result = userInfoMapper.selectOne(4);
log.info(result.toString());
}
}
结果:
当只有一个参数的时候,sql的名称可以是任意的。
参数重命名
import com.example.mybatistest.demos.model.UserInfo;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import org.apache.ibatis.annotations.Select;
import java.util.List;
@Mapper
public interface UserInfoMapper {
@Select("SELECT * FROM user_info")
List<UserInfo> selectAll();
@Select("SELECT * FROM user_info where id =#{id}")
UserInfo selectOne(Integer id);
@Select("SELECT * FROM user_info where id =#{userId}")
UserInfo selectOne2(@Param("userId") Integer id);
}
测试代码:
import com.example.mybatistest.demos.model.UserInfo;
import lombok.extern.slf4j.Slf4j;
import org.apache.ibatis.annotations.Mapper;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import java.util.List;
@SpringBootTest
@Mapper
@Slf4j
class UserInfoMapperTest {
@Autowired
private UserInfoMapper userInfoMapper;
@Test
void selectAll() {
List<UserInfo> list = userInfoMapper.selectAll();
log.info(list.toString());
}
@Test
void selectOne() {
UserInfo result = userInfoMapper.selectOne(4);
log.info(result.toString());
}
@Test
void selectOne2() {
UserInfo result = userInfoMapper.selectOne2(4);
log.info(result.toString());
}
}
结果:
(1)增加数据
@Mapper
public interface UserInfoMapper {
@Select("SELECT * FROM user_info")
List<UserInfo> selectAll();
@Select("SELECT * FROM user_info where id =#{id}")
UserInfo selectOne(Integer id);
@Select("SELECT * FROM user_info where id =#{userId}")
UserInfo selectOne2(@Param("userId") Integer id);
@Options(useGeneratedKeys = true,keyProperty = "id")
@Insert("insert into user_info(username,password,age,gender,phone) " +
"values(#{username},#{password},#{age},#{gender},#{phone})")
Integer insert(UserInfo userInfo);
}
测试代码:
@Test
void insert() {
UserInfo userInfo = new UserInfo();
userInfo.setUsername("zhangsan");
userInfo.setPassword("123456");
userInfo.setAge(18);
userInfo.setGender(0);
userInfo.setPhone("15259307885");
Integer result = userInfoMapper.insert(userInfo);
log.info("执行结果为:{}自增id为:{}",result,userInfo.getId());
}
结果:
(2)删除数据
@Delete("delete from user_info where id = #{id}")
Integer delete(Integer id);
测试代码:
@Test
void delete() {
userInfoMapper.delete(5);
}
结果:
(3)修改数据
@Update("update user_info set age = #{age} where id = #{id}")
Integer update(UserInfo userInfo);
测试代码:
@Test
void update() {
UserInfo userInfo = new UserInfo();
userInfo.setId(6);
userInfo.setAge(20);
Integer result = userInfoMapper.update(userInfo);
if (result > 0) {
log.info("数据修改成功!");
}
}
结果:
(4)查询数据
这里需要提及到一个知识点,结果映射。就是前面提到的数据库的结果和Java对象进行映射,用文字来说可能不太能够理解,我们看下面的两个对比图。
我们可以清楚的看到明明数据库有结果但是我们查询出来的结果确是为null。这是因为Mybatis会帮我们将数据库中的字段名和Java对象中的属性名进行映射,如果名称一样的话就会赋值。
1)对mysql查询的结果进行重命名
@Select("SELECT id,username,password,age,gender,phone," +
"delete_flag as deleteFlag ,create_time as createTime,update_time as updateTime" +
" FROM user_info")
List<UserInfo> selectAll();
注意:from之前换行的话有空格不然会报错,写sql语句之前可以先去命令行执行一下
测试代码:
@Test
void selectAll() {
List<UserInfo> list = userInfoMapper.selectAll();
log.info(list.toString());
}
结果:
那兄弟问了,博主博主这太吃操作了,有没有更简单的方法?
有的兄弟,有的。
2)使用@Results注解进行结果映射
使用注解并且对其命名,如果要复用的话需要在复用代码前加上 @ResultMap(value ="名字")
@Results(id = "BaseMap",value = {
@Result(column = "delete_flag",property = "deleteFlag"),
@Result(column = "create_time",property = "createTime"),
@Result(column = "update_time",property = "updateTime")
})
@Select("SELECT * FROM user_info")
List<UserInfo> selectAll2();
@ResultMap(value ="BaseMap")
@Select("SELECT * FROM user_info where id =#{id}")
UserInfo selectOne(Integer id);
测试代码:
@Test
void selectAll2() {
List<UserInfo> list = userInfoMapper.selectAll2();
log.info(list.toString());
}
@Test
void selectOne() {
UserInfo result = userInfoMapper.selectOne(4);
log.info(result.toString());
}
结果:
那兄弟又问了,博主博主这还是太吃操作了,有没有更简单的方法?
有的兄弟,有的。
3)使用配置的方式,自动转驼峰
#配置驼峰自动转换
mybatis.configuration.map-underscore-to-camel-case=true
将这行神奇的代码放入你的配置文件中。然后
4.使用XML的方式来写Mybatis
(1).需要配置数据库
spring.datasource.url=jdbc:mysql://127.0.0.1:3306/mybatis_test?characterEncoding=utf8&useSSL=false
spring.datasource.username=数据库账户
spring.datasource.password=数据库密码
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
(2).指明xml的路径
#配置mybatisxmL的文件路径,在resources/mapper创建所有表的xml文件
mybatis.mapper-locations=classpath:mapper/**Mapper.xml
(3).XML的实现
1)添加mapper接口
@Mapper
public interface UserInfoXMLMapper {
List<UserInfo> selectAll();
}
2) 添加UserInfoXMLMapper.xml
这里要写自己 UserInfoXMLMapper的路径,按住ctrl键点击UserInfoXMLMapper如果能够点击进去就是配置对了。
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.example.mybatistest.demos.mapper.UserInfoXMLMapper">
<select id="selectAll" resultType="com.example.mybatistest.demos.model.UserInfo">
select * from user_info
</select>
</mapper>
测试代码:
@Slf4j
@SpringBootTest
class UserInfoXMLMapperTest {
@Autowired
UserInfoXMLMapper userInfoXMLMapper;
@Test
void selectAll() {
List<UserInfo> results = userInfoXMLMapper.selectAll();
log.info(results.toString());
}
}
结果:
(4).XML的增删改查
1)增加数据
UserInfoXMLMapper.xml代码
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.example.mybatistest.demos.mapper.UserInfoXMLMapper">
<select id="selectAll" resultType="com.example.mybatistest.demos.model.UserInfo">
select * from user_info
</select>
<insert id="insert">
insert into user_info(username,password,age,gender,phone)
values (#{username},#{password},#{age},#{gender},#{phone})
</insert>
</mapper>
UserInfoXMLMapper代码
@Mapper
public interface UserInfoXMLMapper {
List<UserInfo> selectAll();
Integer insert(UserInfo userInfo);
}
测试代码:
@Slf4j
@SpringBootTest
class UserInfoXMLMapperTest {
@Autowired
UserInfoXMLMapper userInfoXMLMapper;
@Test
void selectAll() {
List<UserInfo> results = userInfoXMLMapper.selectAll();
log.info(results.toString());
}
@Test
void insert() {
UserInfo userInfo = new UserInfo();
userInfo.setUsername("wangwu");
userInfo.setPassword("654321");
userInfo.setAge(81);
userInfo.setGender(1);
userInfo.setPhone("12345678910");
Integer result = userInfoXMLMapper.insert(userInfo);
log.info("执行结果为:{}自增id为:{}",result,userInfo.getId());
}
结果:
2)删除数据
UserInfoXMLMapper.xml代码
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.example.mybatistest.demos.mapper.UserInfoXMLMapper">
<select id="selectAll" resultType="com.example.mybatistest.demos.model.UserInfo">
select * from user_info
</select>
<insert id="insert">
insert into user_info(username,password,age,gender,phone)
values (#{username},#{password},#{age},#{gender},#{phone})
</insert>
<delete id="delete">
delete from user_info where id=#{id}
</delete>
</mapper>
UserInfoXMLMapper代码
@Mapper
public interface UserInfoXMLMapper {
List<UserInfo> selectAll();
Integer insert(UserInfo userInfo);
Integer delete(Integer id);
}
测试代码:
@Slf4j
@SpringBootTest
class UserInfoXMLMapperTest {
@Autowired
UserInfoXMLMapper userInfoXMLMapper;
@Test
void selectAll() {
List<UserInfo> results = userInfoXMLMapper.selectAll();
log.info(results.toString());
}
@Test
void insert() {
UserInfo userInfo = new UserInfo();
userInfo.setUsername("wangwu");
userInfo.setPassword("654321");
userInfo.setAge(81);
userInfo.setGender(1);
userInfo.setPhone("12345678910");
Integer result = userInfoXMLMapper.insert(userInfo);
log.info("执行结果为:{}自增id为:{}",result,userInfo.getId());
}
@Test
void delete() {
userInfoXMLMapper.delete(8);
}
结果:
3)修改数据
UserInfoXMLMapper.xml代码
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.example.mybatistest.demos.mapper.UserInfoXMLMapper">
<select id="selectAll" resultType="com.example.mybatistest.demos.model.UserInfo">
select * from user_info
</select>
<insert id="insert">
insert into user_info(username,password,age,gender,phone)
values (#{username},#{password},#{age},#{gender},#{phone})
</insert>
<delete id="delete">
delete from user_info where id=#{id}
</delete>
<update id="update">
update user_info set age = #{age} where id = #{id}
</update>
</mapper>
UserInfoXMLMapper代码
@Mapper
public interface UserInfoXMLMapper {
List<UserInfo> selectAll();
Integer insert(UserInfo userInfo);
Integer delete(Integer id);
Integer update(UserInfo userInfo);
}
测试代码:
@Slf4j
@SpringBootTest
class UserInfoXMLMapperTest {
@Autowired
UserInfoXMLMapper userInfoXMLMapper;
@Test
void selectAll() {
List<UserInfo> results = userInfoXMLMapper.selectAll();
log.info(results.toString());
}
@Test
void insert() {
UserInfo userInfo = new UserInfo();
userInfo.setUsername("wangwu");
userInfo.setPassword("654321");
userInfo.setAge(81);
userInfo.setGender(1);
userInfo.setPhone("12345678910");
Integer result = userInfoXMLMapper.insert(userInfo);
log.info("执行结果为:{}自增id为:{}",result,userInfo.getId());
}
@Test
void delete() {
userInfoXMLMapper.delete(8);
}
@Test
void update() {
UserInfo userInfo = new UserInfo();
userInfo.setAge(100);
userInfo.setId(7);
userInfoXMLMapper.update(userInfo);
}
结果:
4)查询数据
1)对mysql查询的结果进行重命名 2)定义Result 3)使用配置的方式,自动转驼峰
其中1)和3)和注解的方式一样,所以这里就不展示了就只展示第2)种方式
定义Rusult
UserInfoXMLMapper.xml代码
这里需要注意的是查询数据的时候需要使用resultMap或者resultType,这是为了声明返回的结果是什么,然后Mybastis去进行对应的匹配。
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.example.mybatistest.demos.mapper.UserInfoXMLMapper">
<resultMap id="XMLBasemap" type="com.example.mybatistest.demos.model.UserInfo">
<id column="id" property="id"></id>
<result column="delete_flag" property="deleteFlag"></result>
<result column="create_time" property="createTime"></result>
<result column="update_time" property="updateTime"></result>
</resultMap>
<select id="selectAll2" resultMap="XMLBasemap">
select * from user_info
</select>
<select id="selectAll" resultType="com.example.mybatistest.demos.model.UserInfo">
select * from user_info
</select>
<insert id="insert">
insert into user_info(username,password,age,gender,phone)
values (#{username},#{password},#{age},#{gender},#{phone})
</insert>
<delete id="delete">
delete from user_info where id=#{id}
</delete>
<update id="update">
update user_info set age = #{age} where id = #{id}
</update>
</mapper>
UserInfoXMLMapper代码
@Mapper
public interface UserInfoXMLMapper {
List<UserInfo> selectAll2();
List<UserInfo> selectAll();
Integer insert(UserInfo userInfo);
Integer delete(Integer id);
Integer update(UserInfo userInfo);
}
测试代码:
package com.example.mybatistest.demos.mapper;
import com.example.mybatistest.demos.model.UserInfo;
import lombok.extern.slf4j.Slf4j;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import java.util.List;
import static org.junit.jupiter.api.Assertions.*;
@Slf4j
@SpringBootTest
class UserInfoXMLMapperTest {
@Autowired
UserInfoXMLMapper userInfoXMLMapper;
@Test
void selectAll2() {
List<UserInfo> results = userInfoXMLMapper.selectAll2();
log.info(results.toString());
}
@Test
void selectAll() {
List<UserInfo> results = userInfoXMLMapper.selectAll();
log.info(results.toString());
}
@Test
void insert() {
UserInfo userInfo = new UserInfo();
userInfo.setUsername("wangwu");
userInfo.setPassword("654321");
userInfo.setAge(81);
userInfo.setGender(1);
userInfo.setPhone("12345678910");
Integer result = userInfoXMLMapper.insert(userInfo);
log.info("执行结果为:{}自增id为:{}",result,userInfo.getId());
}
@Test
void delete() {
userInfoXMLMapper.delete(8);
}
@Test
void update() {
UserInfo userInfo = new UserInfo();
userInfo.setAge(100);
userInfo.setId(7);
userInfoXMLMapper.update(userInfo);
}
}
结果:
5.#{}和${}的区别
前面我们使用了#{}来传输值,下面我们来看看他们两个的区别
#{}:预编译sql
${}:即时sql
可能大家这样看的不是那么明显,我们将传入值的类型改为String类型然后对比一下结果:
#{}:
${}:
我们可以看到这里换成String类型之后#{}能够成功执行而${}却报错了 。我们把他们运行之后的sql语句直接对比一下。
#{}:SELECT * FROM user_info where username = 'admin'
${}:SELECT * FROM user_info where username = admin
#是会自动将String类型的值加上",而$只是简单的拼接。可能有的人这里会说我直接都用#不就不会出错误了吗?当然不是,比如select * from user_info order by id desc;这个sql语句中desc作为参数的时候就不需要加",还有模糊查询like也是同样的道理。
在使用${}传递参数的时候我们最需要注意的点是sql注入!那什么是sql注入?
比如:select * from user_info where username='' or 1='1';
在sql语句末尾加上or 1='1'是非常危险的操作,因为原本我们只是获取一个数据,加上之后会将所有的数据返回,这就导致了客户的数据泄露这是非常危险的。那为什么$符号会有这个问题但是#符号却不会出现?这里涉及到了预编译sql和即时sql的区别。
预编译sql和即时sql的区别
首先我们需要清楚当我们输入sql语句的时候数据库做了哪些事情?
如上图所示,当我们输入sql语句的时候首先会进行语法解析,然后进行sql优化和sql编译,最后才是sql执行。那预编译sql就会把sql执行前的几个步骤给缓存起来,等到执行sql语句的时候直接将参数值填入然后执行就行了,而即时sql则需要每次都执行以上步骤,这样会使预编译sql比即时sql的性能更高。那在执行sql语句的时候预编译sql像是一个模板,就是把坑提前挖好,然后来了新的sql语句往里面填;而即时sql是拼接。这就导致了预编译sql不会有sql注入的问题但是即时sql会有。
所以当我们在使用$符号的时候需要对参数进行校验防止sql注入,另外在模糊查询的时候需要注意使用数据库本身提供的sql语句:
select * from user_info where username like CONCAT('%','java','%')
6.数据库连接池
(1)什么是数据库连接池
它既然叫池那里面肯定得装东西,那数据库连接池里面装的就是一个一个的数据库连接。它负责分配,管理,释放数据库的连接,允许应用程序重复的使用一个数据库连接而不是重新建立。如下图所示:
有数据库连接池:启动程序的时候,连接池会创建一定量的Connection对象,每次执行sql语句的时候从连接池获取对应的Connection对象然后执行sql,在sql执行完成之后再把Connection对象还给连接池。
无数据库连接池: 每次执行sql语句的时候需要创建连接,然后执行sql语句,最后再释放,每次执行sql语句的时候都需要重复的创建和释放资源。
(2)使用数据库连接池
常见的数据库连接池:C3P0 DBCP Druid Hikari
目前使用最多的是Hikari和Druid
1)Hikari:SpringBoot默认使用的数据库连接池
2)Druid:
如果要更换数据库连接池将下面的依赖引入到pom文件中即可
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
<version>1.1.17</version>
</dependency>