JavaWeb笔记——Mybatis环境、增删查改
- tip1:Springboot的单元测试方式
- tip2:Java HotSpot(TM) 64-Bit Server VM warning: Sharing is only supported for boot loader classes because bootstrap classpath has been appended
- tip3 :idea配置sql语句提示
- tip4 :Cannot invoke "com.liquor.mybatis_01.maper.EmpMapper.delete(java.lang.Integer)" because "this.empMapper" is null
- tip5 :不同springboot版本/单独使用mybatis对参数名称的附加说明
- 一、基础
- 二、Mybatis基础操作
- 备注:本文application.properties文件配置
黑马程序员2023年javaweb网课笔记,自用
tip1:Springboot的单元测试方式
- 在test下有一个springboot自带的测试文件
- 函数带上@Test注释,可以测试函数,测试时会自动带上工程环境
tip2:Java HotSpot™ 64-Bit Server VM warning: Sharing is only supported for boot loader classes because bootstrap classpath has been appended
虽然csdn给出的解决方案是如右链接,解决方案,但本人设置后没能解决这个warning,由于没发现运行带来的问题,暂时保留
tip3 :idea配置sql语句提示
- 选中sql语句,如下操作:
- 如果成功,sql语句中的关键字会显色;如果表名称爆红,则是idea没连上对应的mysql数据库,检查一下相关问题。
tip4 :Cannot invoke “com.liquor.mybatis_01.maper.EmpMapper.delete(java.lang.Integer)” because “this.empMapper” is null
- 错误:尝试调用一个空对象的方法。
- 原因:测试类中创建的变量没使用依赖注入,注意,每一个变量都需要单独写一个依赖注入注释
tip5 :不同springboot版本/单独使用mybatis对参数名称的附加说明
一、基础
1.1 概论
- MyBatis是一款优秀的持久层框架,用于简化JDBC的开发。
1.2 入门程序
流程:
- 准备工作(创建springboot工程、数据库表user、实体类User)
- 引入Mybatis的相关依赖,配置Mybatis(数据库连接信息)
- 编写SQL语句(注解/XML)
- 创建springboot的Moudle,勾选mybatis需要的框架和支持sql的文件
- 上一步勾好后,maven自动生成的依赖文件pom.xml中引入的mybatis依赖
<dependencies>
<!--mybatis的起步依赖-->
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>3.0.2</version>
</dependency>
<!--mysql的驱动包,如果是mysql-connector-java则是上个版本的驱动包-->
<dependency>
<groupId>com.mysql</groupId>
<artifactId>mysql-connector-j</artifactId>
<scope>runtime</scope>
</dependency>
<!--springboot单元测试依赖-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter-test</artifactId>
<version>3.0.2</version>
<scope>test</scope>
</dependency>
</dependencies>
-
连接mysql,创建数据库表user
-
在java->com.xxx->pojo下建立实体类User,和数据库字段名称一致,自生成get、set和toString:
其中:
1)int->Integer
2)varchar(n)->String
3)tinyint unsigned->Short
-
在resources下的application.properties中配置mybatis连接mysql的信息,数据库连接的url,把mybatis改成自己的数据库名称:
# 驱动类名称
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
# 数据库连接的url
spring.datasource.url=jdbc:mysql://localhost:3306/mybatis
# 用户名
spring.datasource.username=root
# 密码
spring.datasource.password=12345678
- 在java->com.xxx->mapper下,建立接口UserMapper,使用注释@Mapper,查询语句使用注释@Select(“查询sql语句”)此处注意是注释,不需要分号
@Mapper//运行时自动生成接口的实现类对象,交给IOC容器管理
public interface UserMapper {
@Select("select * from user") // mysql的语句
public List<User> list(); //列表存储查询结果
}
- 测试文件代码:
@SpringBootTest//整合单元测试的注解
class Mybatis01ApplicationTests {
@Autowired//之前使用@Mapper管理,现在可以直接自动注入
private UserMapper userMapper;
@Test
public void testListUser() {
List<User> userList = userMapper.list();//调用函数
userList.stream().forEach(user->{
System.out.println(user.toString());
});
}
}
- 测试结果:
1.3 JDBC(仅了解,和mybatis对比)
- JDBC: ( Java DataBase Connectivity ),就是使用Java语言操作关系型数据库的一套API
- JDBC程序示例:繁琐臃肿,频繁开关流浪费资源
@Test
public void testJdbc() throws Exception {
//1. 注册驱动
Class.forName("com.mysql.cj.jdbc.Driver");
//2. 获取连接对象
String url = "jdbc:mysql://localhost:3306/db01";
String username = "root";
String password = "123";
Connection connection = DriverManager.getConnection(url, username, password);
//3. 获取执行SQL的对象Statement,执行SQL,返回结果
String sql = "select * from user";
Statement statement = connection.createStatement();
ResultSet resultSet = statement.executeQuery(sql);
//4. 封装结果数据
List<User> userList = new ArrayList<>();
while (resultSet.next()){
int id = resultSet.getInt("id");
String name = resultSet.getString("name");
short age = resultSet.getShort("age");
short gender = resultSet.getShort("gender");
String phone = resultSet.getString("phone");
User user = new User(id,name,age,gender,phone);
userList.add(user);
}
//显示结果
System.out.println(userList);
//5. 释放资源
statement.close();
connection.close();
}
1.4 数据库连接池
- 数据库连接池是个容器,负责分配、管理数据库连接(Connection)。
1)资源重用,提高响应速度:允许应用程序重复使用一个现有的数据库连接,而不是再重新建立一个。
2)避免数据库连接遗漏:释放空闲时间超过最大空闲时间的连接 - 数据库接口:DataSource
1)Druid德鲁伊:阿里巴巴开源的数据库连接池项目
2)Hikari(springboot默认)
- 切换德鲁伊:引入依赖
德鲁伊官网:https://github.com/alibaba/druid/tree/master/druid-spring-boot-starter
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
<version>1.2.8</version>
</dependency>
1.5 lombok工具包——简化实体类
- Lombok是一个实用的Java类库,能通过注解的形式自动生成构造器、getter/setter、equals、hashcode、toString等方法,并可以自动化生成日志变量,简化java开发、提高效率。
- 引入依赖:
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
- 注解:@Data中不包含无参和全参的构造,一般在实体类之前声明以下三个注释:
@Data//=@Getter + @Setter + @ToString + @EqualsAndHashCode
@NoArgsConstructor//无参构造
@AllArgsConstructor//全参构造
public class User {
private Integer id;
private String name;
private Short age;
private Short gender;
private String phone;
}
二、Mybatis基础操作
2.1 环境
- 引入对应的起步依赖(mybatis、mysql驱动、lombok)
- application.properties中引入数据库连接信息
- 准备数据库表 emp
- 创建对应的实体类 Emp(实体类属性采用驼峰命名,使用lombok的注释)
- 准备Mapper接口 EmpMapper,使用注释@Mapper
2.2 删除操作
- 在EmpMapper接口中写接口函数使用注释写sql语句,其中占位符使用#{}表示,从函数中传递参数,两者名称保持一致
- 代码:此处返回的int数值是被该语句影响的数据条数
//删除ID大于14的数据
@Delete("delete from tb_emp where id >#{id}")
public int delete(Integer id);
- 在测试文件中,增加EmpMapper类变量,用依赖注入注释;在@Test下写测试函数:
@Autowired
private EmpMapper empMapper;
@Test
public void testDelete(){
int deleteResult = empMapper.delete(14);
System.out.println(deleteResult);
}
- 结果为2,因为大于14的有两条
- 但一般不需要这个返回值,设置为void即可
2.3 日志输出 && 预编译sql
- 可以在application.properties中,打开mybatis的日志,并指定输出到控制台。
#指定mybatis输出日志的位置,输出控制台
mybatis.configuration.log-impl=org.apache.ibatis.logging.stdout.StdOutImpl
- 不需要记住上述配置,左式输入mybatislog,选择-impl的,右式输入std自动填充。
- 测试:删除id大于13的,结果:sql语句,问号是占位符,参数是13,结果是影响一条数据。id需要参数注入,是预编译sql
@Delete("delete from tb_emp where id >#{id}")
public int delete(Integer id);
- sql预编译:
区别:
1)预编译:预编译是在运行之前,直接使用?作为占位符,已经进行了缓存;运行时识别的是普通字符串,直接将数值填入问号占位符处。
2)普通sql语句:在运行时填入参数,再进行语法检查和sql编译缓存,可能会造成sql注入。
3)使用#{}和${}区别:正常情况下使用#{},在动态设置使用 ${}
其中,在面对字符串内的参数时,使用concat字符串拼接函数
concat('%',#{name},'%')
2.4 sql注入
- SQL注入是通过操作输入的数据来修改事先定义好的SQL语句,以达到执行代码对服务器进行攻击的方法。
- sql注入实例:登录界面
1)原sql语句
selet count(*) from emp where username='liquor' and password = '12345678';
2)sql注入的输入方法:账户名随意输入,密码输入如下内容:
'or'1'='1
3)被篡改的语句:如下,篡改了sql语句,后置or ‘1’='1’是恒正确的语句,所以where后的条件已经是恒正确语句。此时该语句进入编译流程,会识别错误。
selet count(*) from emp where username='jinyong' and password = ''or'1'='1';
- 解决方案实例:
1)使用预编译
2)测试:由于预编译使得sql语句的形式固定(已经编译过了,不会识别非法注入的or语句,仅仅是填充参数),输入的参数变为普通的字符串,不会再被注入。
2.5 新增操作
- 插入操作经常涉及到多个参数,可以使用实体类作为形参,在使用占位符预编译时,#{}内的参数名要与实体类中定义的名称一致。
- 实例:增加一个员工信息:
- EmpMapper:因为函数insert中传递数值的形参是实体类Emp,所以在使用占位符预编译时,#{}内的参数名要与实体类中定义的名称一致,这里的参数名和sql数据库什么没关系,重点是传递参数的形参
//新增员工
@Insert("insert into tb_emp(id, username, password, name, gender, image, job, entrydate, dept_id, create_time, update_time) " +
"VALUES (#{id},#{username},#{password}, #{name}, #{gender}, #{image}, #{job}, #{entrydate}, #{deptId}, #{createTime}, #{updateTime})")
public void insert(Emp emp);
- 测试代码:
@Test
public void testInsert(){
Emp emp = new Emp();
emp.setUsername("Tom3");
emp.setName("汤姆3");
emp.setImage("1.jpg");
emp.setGender((short)1);
emp.setJob((short)1);
emp.setEntrydate(LocalDate.of(2000,1,1));
emp.setCreateTime(LocalDateTime.now());
emp.setUpdateTime(LocalDateTime.now());
emp.setDeptId(1);
empMapper.insert(emp);
}
2.6 新增操作下的主键返回功能——@Options
-
描述:在数据添加成功后,需要获取插入数据库数据的主键。
-
问题:在未注释时,可以正常运行,但通过get函数获取null值。在上述增加操作的测试文件中加一句向控制台输出id的语句行,获取id=null;
-
语法:会自动将生成的主键值,赋值给对象与之对应的属性变量
@Options(keyProperty = "主键对应的对象变量名",useGeneratedKeys = true)
- 实例:
1)上述新增代码修改:
@Options(keyProperty = "id",useGeneratedKeys = true)
@Insert("insert into tb_emp(id, username, password, name, gender, image, job, entrydate, dept_id, create_time, update_time) " +
"VALUES (#{id},#{username},#{password}, #{name}, #{gender}, #{image}, #{job}, #{entrydate}, #{deptId}, #{createTime}, #{updateTime})")
public void insert(Emp emp);
2)测试代码:
@Test
public void testInsert(){
Emp emp = new Emp();
emp.setUsername("Tom");
emp.setName("汤姆");
emp.setImage("1.jpg");
emp.setGender((short)1);
emp.setJob((short)1);
emp.setEntrydate(LocalDate.of(2000,1,1));
emp.setCreateTime(LocalDateTime.now());
emp.setUpdateTime(LocalDateTime.now());
emp.setDeptId(1);
empMapper.insert(emp);
System.out.println(emp.getId());
}
3)控制台结果:
2.7 更新操作
- 更新一般通过主键查询原数据值并修改
- 实例:通过id更新
1)EmpMapper:
//更新员工
@Update("update tb_emp set username = #{username}, name = #{name}, gender = #{gender}, image = #{image}," +
" job = #{job}, entrydate = #{entrydate}, dept_id = #{deptId},update_time = #{updateTime} " +
"where id = #{id}")
public void update(Emp emp);
2)测试代码:
@Test
public void testUpdate(){
Emp emp = new Emp();
emp.setId(20);
emp.setUsername("T");
emp.setName("汤姆T");
emp.setImage("5.jpg");
emp.setGender((short)1);
emp.setJob((short)1);
emp.setEntrydate(LocalDate.of(2000,1,1));
emp.setCreateTime(LocalDateTime.now());
emp.setUpdateTime(LocalDateTime.now());
emp.setDeptId(1);
empMapper.update(emp);
}
2.8 查询操作 && 字段名称与实体类名不一致问题
- 实例:按照id查询信息
1)EmpMapper:
//根据id查询员工
@Select("select * from tb_emp where id=#{id}")
public Emp select(Integer id);
2)测试代码:
@Test
public void testSelectId(){
Emp selectIdResult = empMapper.select(1);
System.out.println(selectIdResult.toString());
}
3)结果:查询输出的部分数据为null
- 问题原因:
1)实体类属性名 和 数据库表查询返回的字段名一致,mybatis会自动封装。
2)如果实体类属性名 和数据库表查询返回的字段名不一致,不能自动封装。
- 解决方案一:在@select的sql语句中给字段起别名,使得别名与实体类的属性一致,不易于读写代码,不常用
- 方案二:mybatis的注解@Result手动映射封装,代码比较繁琐,使用率不高,其中@Results是集合,column是数据库字段名称,property是映射的名称,与实体类属性名称一致。
@Results({
@Result(column = "dept_id",property = "deptId"),
@Result(column = "create_time",property = "createTime"),
@Result(column = "update_time",property = "updateTime")
})
@Select("select * from tb_emp where id=#{id}")
public Emp select(Integer id);
- 方案三:推荐:开启mybatis驼峰命名自动映射,在application.properties文件中配置,同时,类名和数据库字段名要严格遵守:数据库字段名是下划线连接,属性名是同单词的驼峰
# 开启驼峰命名自动映射
mybatis.configuration.map-underscore-to-camel-case=true
2.9 查询操作——条件查询
- 实例:查找名称中包含汤且性别为1的数据
- EmpMapper:字符串内使用${},不推荐
@Select("SELECT * from tb_emp where name like '%${name}%' and gender = #{gender} order by update_time desc ")
public List<Emp> selectList(String name,short gender);
- 测试代码:
@Test
public void testSelectList(){
List<Emp> emps =empMapper.selectList("汤",(short) 1);
System.out.println(emps);
}
- 使用${}的问题:性能低、不安全、存在SQL注入问题
- 解决办法:concat字符串拼接函数
concat('%',#{name},'%')
@Select("SELECT * from tb_emp where name like concat('%',#{name},'%') and gender = #{gender} order by update_time desc ")
public List<Emp> selectList(String name,short gender);
备注:本文application.properties文件配置
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.url=jdbc:mysql://localhost:3306/数据库名称
spring.datasource.username=root
spring.datasource.password=12345678
# 将mybatis日志输出在控制台
mybatis.configuration.log-impl=org.apache.ibatis.logging.stdout.StdOutImpl
# 驼峰命名自动注入
mybatis.configuration.map-underscore-to-camel-case=true