JavaEE初学07
Mybatis
Mybatis是一款优秀的持久层框架,他支持自定义SQL、存储过程以及高级映射。Mybatis几乎免除了所有的JDBC代码以及设置参数和获取结果集的工作。Mybatis可以通过简单的XML或注解来配置和映射原始类型、接口和Java POJO(Plain Old Java Object,普通老式Java对象)为数据库中的记录。
获取数据库连接有两种方式:(1)DriveManager(2)数据库连接池
区别:
DriveManager每次都创建物理连接,关闭物理连接
数据库连接池在每次初始化时就创建一定数量的数据库连接,每次都从连接池获取连接,每次关闭不关闭物理连接,只是放回连接池(复用提高效率)
项目中,使用双中校检锁的单例模式,创建的是数据库连接池
操作命令对象:(1)简单的 (2)预编译 (3)存储过程
预编译可以提高性能,并且可以防止sql注入(替换占位符,字符串替换会进行转义)
使用Mybatis封装jdbc后,只需要提供: 要执行的SQL语句、要替换占位符的数据(一般为Java对象)、处理结果集(一般转化为Java对象)(查询时返回结果集、增删改时返回更新数量)其它如执行SQL、返回结果集、返回更新数量的步骤由框架来提供
ORM
ORM(Object Relational Mapping)即对象关系映射,将关系型数据库中的数据和对象建立起映射关系,进而自动的完成数据与对象的互相转换
ORM把数据库映射为对象:
数据库表(table) ---> 类(class)
记录(record,行数据) ---> 对象(object)
字段(field) ---> 对象的属性(attribute)
一般的ORM框架,会将数据库模型中每张表都映射为一个类
常见的ORM框架:Mybatis、Hibernate
Mybatis:半自动框架,自己写sql,更方便做sql的性能维护和优化,对关系型数据库模型要求不高,做调整时影响不会很大
缺点:不能跨数据库
Hibernate:全自动框架,自动组装sql语句,可以跨数据库,框架提供了多套主流数据库sql语句生成规则
缺点:学习成本高、对数据库模型依赖很大,需求频繁变更会很难维护和修改、很难定位问题、很难进行性能优化
Mybatis
Mybatis操作配置步骤
创建Maven项目,配置pom.xml文件
准备数据库初始化sql
pom.xml配置
xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<!-- 默认使用的Spring Framework版本为5.2.10.RELEASE -->
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.3.5.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>org.example</groupId>
<artifactId>mybatis-study</artifactId>
<version>1.0-SNAPSHOT</version>
<properties>
<java.version>1.8</java.version>
</properties>
<dependencies>
<!-- spring-boot-starter-web: 基于SpringBoot开发的依赖包,
会再次依赖spring-framework中基本依赖包,aop相关依赖包,web相关依赖包,
还会引入其他如json,tomcat,validation等依赖 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<!-- 排除tomcat依赖 -->
<exclusions>
<exclusion>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-tomcat</artifactId>
</exclusion>
</exclusions>
</dependency>
<!-- 添加 Undertow 依赖 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-undertow</artifactId>
</dependency>
<!--引入AOP依赖-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
<!-- mybatis-spring-boot-starter: Mybatis框架在SpringBoot中集成的依赖包,
Mybatis是一种数据库对象关系映射Object-Relationl Mapping(ORM)框架,
其他还有如Hibernate等 -->
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.1.3</version>
</dependency>
<!-- Mybatis代码生成工具 -->
<dependency>
<groupId>org.mybatis.generator</groupId>
<artifactId>mybatis-generator-core</artifactId>
<version>1.3.5</version>
</dependency>
<!-- druid-spring-boot-starter: 阿里Druid数据库连接池,同样的运行时需要 -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
<version>1.2.3</version>
</dependency>
<!-- JDBC:mysql驱动包 -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.49</version>
<scope>runtime</scope>
</dependency>
<!-- spring-boot-devtools: SpringBoot的热部署依赖包 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<!-- 不能被其它模块继承,如果多个子模块可以去掉 -->
<optional>true</optional>
</dependency>
<!-- lombok: 简化bean代码的框架 -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<!-- spring-boot-starter-test: SpringBoot测试框架 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<!-- SpringBoot的maven打包插件 -->
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
<!-- 明确指定一些插件的版本,以免受到 maven 版本的影响 -->
<plugin>
<artifactId>maven-clean-plugin</artifactId>
<version>3.1.0</version>
</plugin>
<plugin>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.8.1</version>
</plugin>
<plugin>
<artifactId>maven-deploy-plugin</artifactId>
<version>2.8.2</version>
</plugin>
<plugin>
<artifactId>maven-install-plugin</artifactId>
<version>2.5.2</version>
</plugin>
<plugin>
<artifactId>maven-jar-plugin</artifactId>
<version>3.2.0</version>
</plugin>
<plugin>
<artifactId>maven-resources-plugin</artifactId>
<version>3.1.0</version>
</plugin>
<plugin>
<artifactId>maven-site-plugin</artifactId>
<version>3.3</version>
</plugin>
<plugin>
<artifactId>maven-surefire-plugin</artifactId>
<version>2.22.2</version>
</plugin>
</plugins>
</build>
</project>
sql创建
sql
drop database if exists mybatis_study;
create database mybatis_study character set utf8mb4;
use mybatis_study;
drop table if exists user;
create table user(
id int primary key auto_increment,
username varchar(20) not null unique comment '账号',
password varchar(20) not null comment '密码',
nickname varchar(20) comment '用户昵称',
sex bit default 0 comment '性别,0/false为女,1/true为男',
birthday date comment '生日',
head varchar(50) comment '头像地址',
create_time timestamp default now() comment '创建日期,默认为插入时的日期'
) comment '用户表';
drop table if exists article;
create table article(
id int primary key auto_increment,
title varchar(20) not null comment '文章标题',
content mediumtext not null comment '文章内容',
view_count int default 0 comment '文章浏览量',
user_id int comment '外键:用户id',
create_time timestamp default now() comment '创建日期,默认为插入时的日期',
foreign key(user_id) references user(id)
) comment '文章表';
insert into user(username, password) values ('a1', '11');
insert into user(username, password) values ('a2', '12');
insert into user(username, password) values ('b', '2');
insert into user(username, password) values ('c', '3');
insert into article(title, content, user_id) value ('快速排序', 'public ...', 1);
insert into article(title, content, user_id) value ('冒泡排序', 'private ...', 1);
insert into article(title, content, user_id) value ('选择排序', 'private ...', 1);
insert into article(title, content, user_id) value ('归并排序', 'public ...', 2);
insert into article(title, content, user_id) value ('插入排序', 'protected ...', 2);
insert into article(title, content, user_id) value ('希尔排序', 'protected ...', 3);
insert into article(title, content, user_id) value ('List', 'public ...', 4);
insert into article(title, content, user_id) value ('Set', 'public ...', 4);
insert into article(title, content, user_id) value ('Map', 'public ...', 4);
创建启动类
java
@SpringBootApplication
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class,args);
}
}
配置文件
java
logging.level.root=debug
logging.level.druid.sql.Statement=DEBUG
logging.level.org.example=DEBUG
spring.datasource.driver-class-name=com.mysql.jdbc.Driver
spring.datasource.url=jdbc:mysql://localhost:3306/mybatis_study?useUnicode=true&characterEncoding=UTF-8&useSSL=false
spring.datasource.username=root
spring.datasource.password=root
mybatis.mapper-locations=classpath:mapper/**Mapper.xml
创建数据库实体类
java
@Getter
@Setter
@ToString
public class User {
private Integer id;
private String username;
private String password;
private String nickname;
private boolean sex;
private Date birthday;
private String head;
private Date create_time;
}
java
@Getter
@Setter
@ToString
public class Article {
private Integer id;
private String title;
private String content;
private Integer view_count;
private Integer user_id;
private Date create_time;
}
创建mapper接口方法
在这个接口方法里,返回值就是jdbc操作后的返回:
查询时:如果结果集是一行数据,则返回一个对象,若查不到则返回null
如果结果集是多行数据,则返回一个List<类型>,查不到则返回一个空的List<类型>
更新时:返回int,更新多少条就返回多少
方法参数:要替换占位符的值
java
@Mapper
@Component
public interface ArticleMapple {
Article selectById(Integer id);
}
java
@Mapper
@Component
public interface UserMapper {
User selectById(Integer id);
}
配置实体映射文件
在application.properties
配置文件中配置的文件mybatis.mapper-locations=classpath:mapper/**Mapper.xml
ArticleMapper.xml
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="org.example.mapper.ArticleMapper">
<resultMap id="BaseResultMap" type="org.example.model.Article">
<id column="id" property="id"/><!--主键字段-->
<result column="title" property="title"/><!--普通字段-->
<result column="content" property="content"/>
<result column="view_count" property="view_count"/>
<result column="user_id" property="user_id"/>
<result column="creat_time" property="creat_time"/>
</resultMap>
</mapper>
UserMapper.xml
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="org.example.mapper.UserMapper">
<resultMap id="BaseResultMap" type="org.example.model.User">
<id column="id" property="id"/><!--主键字段-->
<result column="username" property="username"/><!--普通字段-->
<result column="password" property="password"/>
<result column="nickname" property="nickname"/>
<result column="sex" property="sex"/>
<result column="birthday" property="birthday"/>
<result column="head" property="head"/>
<result column="creat_time" property="creat_time"/>
</resultMap>
</mapper>
在实体映射文件添加接口中方法对映的增删改查字段,如:
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="org.example.mapper.ArticleMapper">
<resultMap id="BaseResultMap" type="org.example.model.Article">
<id column="id" property="id"/><!--主键字段-->
<result column="title" property="title"/><!--普通字段-->
<result column="content" property="content"/>
<result column="view_count" property="view_count"/>
<result column="user_id" property="user_id"/>
<result column="creat_time" property="creat_time"/>
</resultMap>
<!--增删改查对应 <insert>、<delete>、<update>、<select>。方法名和id一致-->
<select id="selectById" resultMap="BaseResultMap">
select * from article where id = #{id}
</select>
</mapper>
UserMapper.xml 同理,添加对应select标签
当sql操作为更新操作时,标签内容应变为:
xml
<update id="updateContentById" parameterType="org.example.model.Article">
update article set content = #{content} where id = #{id}
</update>
当接口方法的方法参数为多个时,则需要添加@Param
注解,如:
java
List<Article> selectLikeByTitleAndContent(@Param("title")String title , @Param("content")String content);
传入参数时,有时不需要添加单引号(如排序时desc(倒序)和asc(正序)),则需要使用${}
来进行拼接,如:
java
List<Article> selectLikeByUserIdAndContentOrderBy(@Param("user_id")Integer user_id,@Param("content")String content,@Param("orderBy")String orderBy);
xml
<select id="selectLikeByUserIdAndContentOrderBy" resultMap="BaseResultMap">
select * from article where user_id = #{user_id} and content like #{content} order by id ${orderBy}
</select>
java
@Test
public void selectLikeByUserIdAndContentOrderBy(){
List<Article> list = articleMapper.selectLikeByUserIdAndContentOrderBy(1,"p%","asc");
System.out.println(list);
}
增加数据时获取主键
useGeneratedKeys 这会使用JDBC的getGeneratedKeys 方法来获取由数据库内部生成的主键的值
keyProperty 指定能够唯一识别对象的属性
java
int insertGetIdKey(Article article);
xml
<insert id="insertGetIdKey" parameterType="org.example.model.Article" useGeneratedKeys="true" keyProperty="id">
insert into article(title,content,user_id) values (#{title},#{content},#{user_id})
</insert>
java
@Test
public void insertGetIdKey(){
Article article = new Article();
article.setTitle("123");
article.setContent("123456");
article.setUser_id(1);
articleMapper.insertGetIdKey(article);
System.out.println(article.gerId());//id有值
}
查询结果集,包含两张表,对应两个JAVA类型的数据,他们之间产生了一对一,一对多的关系
如:
select * from user u join article a on u.id = a.user_id
一对一结果映射
返回List<Article>
,以文章返回的主体,就应该表现为一个文章对应一个用户,一对一映射关系
public class Article{
private User user;
}
java
public interface ArticleMapper {
List<Article> selectAll();//查询所有文章信息,并携带用户信息,一对一
}
xml
<resultMap>
<!--把Article类中的user属性,建立一对一映射关系 property选择对应属性 columnPrefix前缀匹配(查找后哪个信息是user属性的属性) resultMap结果集映射-->
<association property="user" columnPrefix="u_" resultMap="org.example.mapper.UserMapper.BaseResultMap"/>
</resultMap>
<select id="selectAll" resultMap="BaseResultMap">
select a.*,u.id u_id,u.username u_username,u.password u_password,u.nickname u_nickname,u.sex u_sex,u.birthday u_birthday,u.head u_head,u.create_time u_create_time from user u join article a on u.id = a.user_id
</select>
java
@Test
public void selectAll(){
List<Article> articles = articleMapper.selectAll();
articles.forEach(System.out::println);
}
一对多结果映射
因为一个用户对应多个文章,所以返回的类型应该为List<User>
,
java
public class User{
List<Article> articles;
}
java
List<User> selectAll1();
xml
<mapper namespace="org.example.mapper.UserMapper">
<resultMap id="BaseResultMap" type="org.example.model.User">
<!--一对多映射(此处标签名与一对一映射标签名不一致 注意)-->
<collection property="article" columnPrefix="a_" resultMap="org.example.mapper.ArticleMapper.BaseResultMap"/>
</resultMap>
<select id="selectAll1" resultMap="BaseResultMap">
select u.*,a.id a_id,a.title a_title,a.content a_content,a.view_count a_view_count,a.user_id a_user_id,a.create_time a_create_time from article a join user u on a.user_id = u.id
</select>
java
@Test
public void selectAll1(){
List<User> users = userMapper.selectAll1();
users.forEach(System.out::println);
}
association 一对一标签 对象中包含另一个对象
collection 一对多标签 对象中包换List<另一个对象>
框架本质上是在运行期基于接口生成代理类,把接口方法生成具体的实现。找到sql,执行sql前替换占位符,执行完转换为java对象。
Mybatis动态sql
本质上就是根据不同条件,循环来动态的拼接sql语句
if标签
在插入一条数据时,有时会存在空的属性,此时如果插入为null,则不能自动插入为默认值,如果需要自动变为默认值,则不应该插入。此时就可以使用<if>
如:
xml
<insert id="insertIf" useGeneratedKeys="true" keyProperty="id">
insert into article(
title,content,user_id
<if test="view_count!=null">
,view_count
</if>
) values (
#{title},#{content},#{user_id}
<if test="view_count!=null">
,#{view_count}
</if>)
</insert>
trim标签
通常情况下,一个字段玄天时,一般使用if标签,多个字段需要选填时,一般使用trim标签
标签属性如下:
prefix:表示整个语句块外部,以prefix的值作为前缀
suffix:表示整个语句块外部,以suffix的值作为后缀
prefixOverrides:表示整个语句块内部需要去除的前缀
suffixOverrides:表示整个语句块内部需要去除的后缀
<insert id="insertTrim" useGeneratedKeys="true" keyProperty="id">
insert into article
<trim prefix="(" suffix=")" suffixOverrides=",">
<if test="title!=null">title,</if>
<if test="content!=null">content,</if>
<if test="user_id!=null">user_id,</if>
<if test="view_count!=null">view_count,</if>
</trim>
values
<trim prefix="(" suffix=")" suffixOverrides=",">
<if test="title!=null">#{title},</if>
<if test="content!=null">#{content},</if>
<if test="user_id!=null">#{user_id},</if>
<if test="view_count!=null">#{view_count},</if>
</trim>
</insert>
where标签
在使用trim标签和if标签时,会存在一种trim标签中全部都是if标签,如果此时if标签全为null,那么就没有对应的条件,这样一来就会出现sql语法错误,此时就可以使用where标签。
where可理解为<trim prefix="where" prefixOverrides="and">
如:
xml
<select id="selectByCondition" resultMap="BaseResultMap">
select * from user
<where>
<if test="id!=null">
and id = #{id}
</if>
<if test="username!=null">
and username = #{username}
</if>
<if test="password!=null">
and password = #{password}
</if>
<if test="nickname!=null">
and nickname = #{nickname}
</if>
<if test="sex!=null">
and sex = #{sex}
</if>
<if test="birthday!=null">
and birthday = #{birthday}
</if>
<if test="head!=null">
and head = #{head}
</if>
<if test="create_time!=null">
and create_time = #{create_time}
</if>
</where>
set标签
根据传入的用户对象属性来更新用户数据,可以使用<set>
标签来指定动态内容
根据传入的id属性,来修改(更新)其它不为null的属性
具体操作为:
xml
<update id="updateBySet" parameterType="org.example.model.Article">
update article
<set>
<trim suffixOverrides=",">
<if test="title!=null">
title = #{title},
</if>
<if test="content!=null">
content = #{content},
</if>
<if test="view_count!=null">
view_count = #{view_count},
</if>
<if test="user_id!=null">
user_id = #{user_id},
</if>
<if test="create_time!=null">
create_time = #{create_time},
</if>
</trim>
</set>
where id = #{id}
</update>
foreach标签
对集合进行遍历可使用foreach标签,foreach有以下属性
collection:绑定方法参数中的集合,如List、Set、Map或数组对象
item:遍历时的每一个对象
open:语句开头的字符串
close:语句结束的字符串
separator:每次遍历之间间隔的字符串
如:批量删除:
java
int deleteByIds(List<Integer> ids);
xml
<delete id="deleteByIds" >
delete from article where id in
<foreach collection="list" item="item" open="(" close="_" separator=",">
#{item}
</foreach>
</delete>
批量插入:
java
int insertBatch(List<Article> articles);
xml
<insert id="insertBatch">
insert into article (title,content,user_id) values
<foreach collection="list" item="item" separator=",">
(#{item.title},#{item.content},#{item.user_id})
</foreach>
</insert>
java
@Test
public void insertBatch() {
Article a1 = new Article();
a1.setTitle("l");
a1.setContent("llllll");
a1.setUser_id(1);
Article a2 = new Article();
a2.setTitle("i");
a2.setContent("iiiiiii");
a2.setUser_id(2);
Article a3 = new Article();
a3.setTitle("t");
a3.setContent("ttttttt");
a3.setUser_id(3);
System.out.println(articleMapper.insertBatch(Arrays.asList(a1,a2,a3)));
List<Article> articles = articleMapper.selectAll();
articles.forEach(System.out::println);
}
补充
JDBC的操作步骤
(1)创建数据库连接池DataSource
(2)通过DataSource获取数据库链接Connection
(3)编写执行带?
占位符的SQL语句
(4)通过Connection及SQL创建操作命令对象Statement
(5)替换占位符:指定要替换的数据库字段类型,占位符索引及要替换的值
(6)使用Statement执行SQL语句
(7)查询操作:返回结果集ResultSet,更新操作:返回更新的数量
(8)处理结果集
(9)释放资源
占位符
占位符要替换的值:传入的java对象:(插入的值、修改的值、条件字段的值)
数据库初始化
打开数据库配置界面
点击左上角+号 选择Mysql
选择驱动程序
下载驱动文件
输入用户名密码后点击测试链接,出现绿色勾后点击确定建立连接
右击sql文件,点击RUN
点击加号 双击刚才建立的连接 点击确定就初始化完成了
配置启动类
配置完映射文件后,如果需要测试相关配置是否正确,可以在test目录下来进行测试
操作如下:
创建测试类
java
@RunWith(SpringRunner.class)
@SpringBootTest(classes = Application.class)
@Transactional//使用事务在测试代码,会自动回滚
public class ArticleMapperTest {
@Autowired
private ArticleMapper articleMapper;
@Test
public void selectById(){
Article a = articleMapper.selectById(1);
System.out.println(a);
}
}
右击运行即可
特殊查找
若查找为模糊查找可以用下列两种方法,
(1)可直接把字符串写为"select * from user where username like #{username}"
传入参数时username="a%"
(2)也可在sql语句中进行拼接select * from user where username like concat(${username},'%')
但是,这种方式存在sql注入的风险
如果只有一个参数(类型为包装类型、基本数据类型),xml中变量名不作要求(xml中变量名可以随便写,如:id=#{aaa}
也可以直接获取到)
如果只有一个参数,且类型为POJO类型,xml中,占位符变量名为对象中的属性
如果有多个参数,必须在接口方法参数上,使用@Param("变量名")
如果有多个参数(类型为包装类型、基本数据类型),xml中应写为#{变量名}
如果只有多个参数,且类型为POJO类型,xml中应写为#{变量名.属性}
当被Param注解时,注解内容名称就必须跟xml中通配符的名称一致
多个参数且类型为POJO:
java
List<Article> selectLikeByUserIdAndContent(@Param("user") User user , @Param("content")String content);
xml
<select id="selectLikeByUserIdAndContent" resultMap="BaseResultMap">
select * from article where user_id = #{user.id} and content like #{content}
</select>
占位符
#{变量名}
会先替换为?,再替换值,所以,替换的字符串值,会带上单引号来替换
${变量名}
会以字符串拼接的方式来替换,所以,替换的字符串值,没有单引号,故存在sql注入的风险