JavaEE初学07
- Mybatis
- ORM
- Mybatis
- 一对一结果映射
- 一对多结果映射
- Mybatis动态sql
- if标签
- trim标签
- where标签
- set标签
- foreach标签
- 补充
- 在这里插入图片描述
- 右击运行即可 ![在这里插入图片描述](https://i-blog.csdnimg.cn/direct/c71d44d027374a399d5d537ce96f00e1.png)
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 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创建
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);
创建启动类
@SpringBootApplication
public class Application {public static void main(String[] args) {SpringApplication.run(Application.class,args);}
}
配置文件
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
创建数据库实体类
@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;
}
@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,更新多少条就返回多少
方法参数:要替换占位符的值
@Mapper
@Component
public interface ArticleMapple {Article selectById(Integer id);
}
@Mapper
@Component
public interface UserMapper {User selectById(Integer id);
}
配置实体映射文件
在application.properties
配置文件中配置的文件mybatis.mapper-locations=classpath:mapper/**Mapper.xml
ArticleMapper.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 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 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操作为更新操作时,标签内容应变为:
<update id="updateContentById" parameterType="org.example.model.Article">update article set content = #{content} where id = #{id}</update>
当接口方法的方法参数为多个时,则需要添加@Param
注解,如:
List<Article> selectLikeByTitleAndContent(@Param("title")String title , @Param("content")String content);
传入参数时,有时不需要添加单引号(如排序时desc(倒序)和asc(正序)),则需要使用${}
来进行拼接,如:
List<Article> selectLikeByUserIdAndContentOrderBy(@Param("user_id")Integer user_id,@Param("content")String content,@Param("orderBy")String orderBy);
<select id="selectLikeByUserIdAndContentOrderBy" resultMap="BaseResultMap">select * from article where user_id = #{user_id} and content like #{content} order by id ${orderBy}</select>
@Testpublic void selectLikeByUserIdAndContentOrderBy(){List<Article> list = articleMapper.selectLikeByUserIdAndContentOrderBy(1,"p%","asc");System.out.println(list);}
增加数据时获取主键
useGeneratedKeys 这会使用JDBC的getGeneratedKeys 方法来获取由数据库内部生成的主键的值
keyProperty 指定能够唯一识别对象的属性
int insertGetIdKey(Article article);
<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>
@Testpublic 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;
}
public interface ArticleMapper {List<Article> selectAll();//查询所有文章信息,并携带用户信息,一对一
}
<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>
@Testpublic void selectAll(){List<Article> articles = articleMapper.selectAll();articles.forEach(System.out::println);}
一对多结果映射
因为一个用户对应多个文章,所以返回的类型应该为List<User>
,
public class User{List<Article> articles;
}
List<User> selectAll1();
<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>
@Testpublic void selectAll1(){List<User> users = userMapper.selectAll1();users.forEach(System.out::println);}
association 一对一标签 对象中包含另一个对象
collection 一对多标签 对象中包换List<另一个对象>
框架本质上是在运行期基于接口生成代理类,把接口方法生成具体的实现。找到sql,执行sql前替换占位符,执行完转换为java对象。
Mybatis动态sql
本质上就是根据不同条件,循环来动态的拼接sql语句
if标签
在插入一条数据时,有时会存在空的属性,此时如果插入为null,则不能自动插入为默认值,如果需要自动变为默认值,则不应该插入。此时就可以使用<if>
如:
<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">
如:
<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的属性
具体操作为:
<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:每次遍历之间间隔的字符串
如:批量删除:
int deleteByIds(List<Integer> ids);
<delete id="deleteByIds" >delete from article where id in<foreach collection="list" item="item" open="(" close="_" separator=",">#{item}</foreach></delete>
批量插入:
int insertBatch(List<Article> articles);
<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>
@Testpublic 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目录下来进行测试
操作如下:
创建测试类
@RunWith(SpringRunner.class)
@SpringBootTest(classes = Application.class)
@Transactional//使用事务在测试代码,会自动回滚
public class ArticleMapperTest {@Autowiredprivate ArticleMapper articleMapper;@Testpublic 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:
List<Article> selectLikeByUserIdAndContent(@Param("user") User user , @Param("content")String content);
<select id="selectLikeByUserIdAndContent" resultMap="BaseResultMap">select * from article where user_id = #{user.id} and content like #{content}</select>
占位符
#{变量名}
会先替换为?,再替换值,所以,替换的字符串值,会带上单引号来替换
${变量名}
会以字符串拼接的方式来替换,所以,替换的字符串值,没有单引号,故存在sql注入的风险