应用分层的请求流程:浏览器发起请求,先请求Controller,Controller收到请求调用Service进行逻辑处理,Service再调用Dao来对数据库中的数据进行处理,本篇文章就来学习对数据库的如何进行操作。
目录
一、什么是MyBatis
持久层
MyBatis入门
创建工程
数据准备
配置数据库连接字符串
持久层代码
测试
二、MyBatis的增删改查
参数传递
增(Insert)
删(Delete)
改(Update)
查(Select)
三、MyBatis XML
增删改查
查询的结果映射
四、#{}与${}
#{}与${}的使用
区别
五、数据库连接池
介绍
总结
一、什么是MyBatis
MyBatis是一款优秀的持久层框架,用于简化JDBC开发,使用JDBC来操作数据库整个过程非常繁琐,我们不旦要拼接每一个参数,而且还要按照模板代码的方式一步一步操作数据库,使用MyBatis看看可以帮助我们更方便更快速的操作数据库。
MyBatis本来是Apache的一个开源项目,2010年这个项目由apache迁移到了google code,并改名为MyBatis,2013年迁移到了Github。官网:MyBatis官网
持久层
在上面我们提到了MyBatis是一个持久层框架,持久层:持久化操作的层,通常指数据访问层,是用来操作库的。
MyBatis更简单的完成程序和数据库交互的框架,也就是更简单的操作和读取数据库的工具,接下来我们通过一个程序来感受如何通过MyBatis操作数据库
MyBatis入门
创建工程
创建springboot工程并导入mybatis依赖以及mysql驱动包
项目工程创建完成后自动会在pom文件中导入MyBatis依赖和MySQL驱动依赖
SpringBoot与MyBatis的对应关系参考:对应关系
数据准备
在mysql中创建用户表并创建对应的实体类User
操作在这:
sql">-- 创建数据库DROP DATABASE IF EXISTS mybatis_test;
CREATE DATABASE mybatis_test DEFAULT CHARACTER SET utf8mb4;-- 使用数据数据
USE mybatis_test;-- 创建表[用户表]
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' );
接下来在java中以持久层的方式创建model与mapper包,在model中创建实体类UserInfo,实体类达到属性名要与表中的字段名一一对应
配置数据库连接字符串
MyBatis要连接数据库需要数据库相关的参数配置:MySql驱动类/登录名/密码/数据库连接字符串
我们使用yml格式的配置文件,配置内容:
spring:datasource:url: jdbc:mysql://127.0.0.1:3306/mybatis_test?characterEncoding=utf8&useSSL=falseusername: rootpassword: rootdriver-class-name: com.mysql.cj.jdbc.Driver
持久层代码
在项目中创建持久层接口UserInfoMapper
MyBatis的持久层接口规范一般都叫xxxMapper
@Mapper注解:表示是MyBatis中的Mapper接口
程序运行时,框架会自动生成接口的实现类对象并交给Spring管理
package com.example.batisdemo.mapper;import com.example.batisdemo.model.UserInfo;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Select;import java.util.List;@Mapper
public interface UserInfoMapper {@Select("select username ,age,gender,phone from user_info")//select人如其名,就是用来对数据库进行select操作的注解,括号内为sql语句public List<UserInfo> queryList();
}
测试
创建出来的SpringBoot工程中,在src的test目录下自动帮我们创建好了测试类,我们使用这个测试类来测试,鼠标放在要测试的方法中,右键generate选择test就帮我们创建好了test方法。
package com.example.batisdemo.mapper;import com.example.batisdemo.model.UserInfo;
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
class UserInfoMapperTest {@AutowiredUserInfoMapper userInfoMapper;@Testvoid contextLoads() {List<UserInfo> userInfoList = userInfoMapper.queryList();//接收的信息不止一条,所以我们使用list来对数据进行接收System.out.println(userInfoList);}
}
运行我们想要测试的方法,可以得到数据库中的信息
二、MyBatis的增删改查
参数传递
需求:当想要查找id为4的用户时,对应的SQL就是:select * from user_info where id = 4
@Select("select username,age,gender,phone from user_info where id = 4")public List<UserInfo> queryID();
但是这样代码就写死了只能查找id为4的数据,所以sql语句中的id值不能写成固定值
解决方案:在queryID方法中添加参数(id),将方法中的参数传给sql语句,sql语句中使用#{}来获取方法中的数;代码改为:
增(Insert)
sql中的插入语句返回的影响的行数,所以我们用Integer来接收方法的返回值
Mapper接口:
@Insert("insert into user_info(username,age,gender,password) values (#{username},#{age},#{gender},#{password})")public Integer insert(UserInfo userInfo);
因为传入的参数较多,我们直接使用对象来传递参数
测试代码:
@Testvoid insert() {UserInfo userInfo = new UserInfo();userInfo.setAge(18);userInfo.setGender(0);userInfo.setUsername("fuwanxing");userInfo.setPassword("fuwanxing");Integer insert = userInfoMapper.insert(userInfo);System.out.println(insert);}
结果为:
Insert语句默认返回的是受影响的行数,但有些时候我们在数据插入后需要获取到新插入数据的id,此时需求是拿到自增id,解决方法:在Mapper接口上添加一个options的注解
@Options(useGeneratedKeys = true,keyProperty = "id")@Insert("insert into user_info(username,age,gender,password) values (#{username},#{age},#{gender},#{password})")public Integer insert(UserInfo userInfo);
useGeneratedKeys:取出数据库内部生成的主键,默认值false;
keyProperty:指定能够唯一识别对象的属性
运行结果与测试数据:
设置useGenertedKeys = true后方法返回值依然是受影响的行数,自增id会设置在上述keyproperty指定的属性上。
删(Delete)
Mapper接口:
@Delete("delete from user_info where id = #{id}")void delete(Integer id);
改(Update)
Mapper接口
@Update("update user_info set username = #{username} where id = #{id}")void update(UserInfo userInfo);
查(Select)
我们在上面查询时发现有几个字段是没有赋值的,因为Java对象属性和数据库字段一模一样时才会进行赋值,我们多查询一些数据看看:
@Select("select username ,age,gender,phone,delete_flag,create_time,update_time from user_info")public List<UserInfo> queryList();
查询结果:
从运行结果来看可以看到我们带下划线的三个属性没有赋值,是因为当自动映射查询结果时,MyBatis会获取结果中返回的列名并在Java类中查找相同名字的属性,也就是数据库中属性名是没有下划线的,所以映射失败了
解决方法:
起别名
在sql语句中使用as给列名起别名,让别名与属性名保持一致
@Select("select username ,age,gender,phone,delete_flag as deleteflag,create_time as createtime,update_time as updatetime from user_info")public List<UserInfo> queryList();
结果映射
使用@Result注解标注出映射关系
@Select("select username ,age,gender,phone,delete_flag,create_time,update_time from user_info")@Results({@Result(column = "delete_flag",property = "deleteflag"),@Result(column = "create_time",property = "createflag"),@Result(column = "update_time",property = "updatetime")})public List<UserInfo> queryList();
开启驼峰命名(推荐)
通常数据库列使用蛇形命名法进行命名,而Java一般遵循驼峰命名法约定,为了在这两种命名方式之间启动自动映射,可以添加配置将mapUnderscoreToCamelCase设置为true。
mybatis:mapper-locations: classpath:mapper/*Mapper.xmlconfiguration: # 配置打印 MyBatis 执行的 SQLlog-impl: org.apache.ibatis.logging.stdout.StdOutImplmap-underscore-to-camel-case: true #自动驼峰转换
此时java代码不需做任何处理
三、MyBatis XML
MyBatis的开发有两种方式:注解/XML,通过注解的方式主要完成一些简单的增删改查功能,如果需要实现复杂的SQL功能,建议使用XML来配置映射语句,也就是将SQL语句写在XML配置文件中。
MyBatisXML的方式需要以下两步:1、配置数据库连接字符串和MyBatis 2、写持久层代码。
配置连接我们在上面已经说过了,接下来我们写持久层代码
持久层代码分为两部分:1、方法实现Interface 2、方法实现:xxx.xml
1、添加mapper接口
@Mapper
public interface UserInfoMapper {List<UserInfo> queryAll();
}
2、添加UserInfoXMLMapper.xml
数据持久层的实现,MyBatis的固定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.bite.mybatis.mapper.UserInfoXmlMapper"></mapper>
注意xml文件的位置与yml中的路径保持一致
查询所有用户的实现:
<?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.bite.mybatis.mapper.UserInfoXmlMapper"><select id = "queryAll" resultType="com.example.batisdemo.model.UserInfo">select username,password,age,gender from user_info</select></mapper>
<mapper>:需要指定namespace属性,表示命名空间,值为mapper接口的全限定名
<select>:查询标签,想要进行什么样的操作使用什么样的标签;
id:和接口中定义的方法名称一样,表示对接口方法的具体实现
resultType:返回数据的类型,也就是我们定义的实体类,也是全限定类名
测试
增删改查
接下来我们来实现用户的增加操作
xml实现
<insert id = "insert">insert into user_info (username,password,gender,age) values (#{username},#{password},#{gender},#{age})</insert>
接口
Integer insert(UserInfo userInfo);
测试方法
@Testvoid insert() {UserInfo userInfo = new UserInfo();userInfo.setUsername("yuanji");userInfo.setPassword("yuanji");userInfo.setGender(1);userInfo.setAge(18);userInfoXMLMapper.insert(userInfo);}
删除和更新与上面别无二致,接下来我们看查找的结果映射
查询的结果映射
xml来写结果映射使用标签<resultMap>
<resultMap id = "BaseMap" type="com.example.batisdemo.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="queryAllUser" resultMap="BaseMap">
select id, username,`password`, age, gender, phone, delete_flag,create_time, update_time from user_info
</select>
四、#{}与${}
#{}与${}的使用
先看Integer类型的参数
@Select("select username,age,gender,phone from user_info where id = #{userID}")public List<UserInfo> queryID(Integer userId);
看我们打印的日志可以发现我们输入的参数并没有在后面拼接,id的值使用?进行占位,我们称之为预编译SQL
我们把#{}换成${}再看日志:
这次参数直接拼接在SQL语句中了
接下来我们再看String类型的参数
使用#{}结果正常返回了,改成${}再观察日志
可以看到这次参数直接拼在了SQL语句中,但是字符串作为参数需要添加引号' ',使用$不会拼接引号导致程序报错,修改方案是在$外加一层引号即可正常返回 从上面两个栗子可以看出,#{}使用的预编译SQL通过?占位的方式提前对SQL进行编译,然后把参数填充到SQL语句中,会根据参数类型自动拼接引号,而${}会直接进行字符串替换,如果参数为字符串需要加上引号' '
区别
#{}/${}区别就是预编译SQL和即使SQL的区别:
1、性能更高:绝大多数情况下某一条SQL语句可能会被反复调用执行,或者每次执行的时候只有个别的值不同,如果每次都需要经过上面的语法解析等操作,效率明显就不行了。
预编译SQL会在编译一次后将编译后的SQL语句缓存起来,后面再执行这条语句时不会再次编译
2、更安全(防止SQL注入)
SQL注入:通过操作输入的数据来修改事先定义好的SQL语句以达到执行代码对服务器进行攻击的方法
${}会有SQL注入的风险,尽量使用#{}完成查询
3、排序功能
虽然${}有风险,但它也有它的用武之地
使用${}的场景
mapper代码
@Select("select id, username, age, gender, phone, delete_flag, create_time, update_time "+"from user_info order by id ${sort} ")List<UserInfo> queryAllUserBySort(String sort);
sort中填入desc/esc来实现顺序或逆序,使用${}可以实现排序查询,而#{}会自动为esc加上引号导致报错或无法实现功能
五、数据库连接池
使用数据库连接池技术可以避免频繁的创建连接摧毁链接
介绍
数据库池负责分配、管理和释放数据库连接,它允许应用程序重复使用一个现有的数据库连接而不是重新再建议一个
没有数据库连接池的情况:每次执行SQL语句要先创建一个新的连接对象然后执行sql语句,sqk语句执行完再关闭连接对象释放资源,这种重复的创建连接摧毁连接比较消耗资源
使用数据库连接池的情况:程序启动时,会在数据库连接池中创建一定数量的Connection对象,当客户请求数据库连接池,会从数据库连接池中获取连接对象,然后执行sql,sql执行完再把connection归还给连接池
优点:减少了网络开销/资源重用/提升了系统性能
常见的数据库连接池:C3P0/DBCP/Druid/Hikari
总结
1、mysql开发企业规范:表名/字段名使用小写字母或数字,单词间以下划线分割;表名必备三字段:id/create_time/update_time;在表查询中,避免使用*作为查询的字段列表
2、#{}/&{}区别
(a)#{}预编译处理,${}字符直接替换
(b)#{}可以防止sql注入,${}存在sql注入风险,查询语句中推荐使用#{}
(c)排序功能/表名/字段名作为参数时需要使用${}
感谢观看
道阻且长,行则将至