SSM框架_Mybatis
环境准备 [Tomcat + Maven]:
1.安装Tomcat:
如何安装和配置两个不同版本的tomcat, 参考链接如下:
https://blog.csdn.net/weixin_42124622/article/details/82023344
2.安装maven: 下载maven ->设置环境变量 ->更改为阿里云仓库 ->配置idea;
2.1 设置maven本地仓库地址:
<localRepository>D:\MyAllCode\maven\mavenRepository</localRepository>
2.2 在<mirrors>处设置国内镜像源:
<mirrors>
<!-- 阿里云旧版仓库 -->
<mirror>
<id>alimaven</id>
<name>aliyun maven</name>
<url>http://maven.aliyun.com/nexus/content/groups/public/</url>
<mirrorOf>central</mirrorOf>
</mirror>
<!-- 阿里云新版仓库
<mirror>
<id>aliyunmaven</id>
<mirrorOf>*</mirrorOf>
<name>阿里云公共仓库</name>
<url>https://maven.aliyun.com/repository/public</url>
</mirror>
-->
</mirrors>
2.3 [解决在IDEA中每次都要重复配置Maven]: File-> New Projects Settings-> Settings for Project...-> 修改Maven相关配置.
3.ssm框架中常用的东西:
loombook:
3.1 用于实体类自动生成有参无参构造器和get/set方法;
(常用注解: @Data, @AllArgsConstructor, @NoArgsConstructor);
3.2 使用lombok需要安装它的插件和在pom.xml中导入jar包;
junit: 编写测试类;[旧版为junit4; 新版junit5为junit ]
一.MyBatis
1.1 工具类获取SqlSession:
//SqlSessionFactoryBuilder -> SqlSessionFactory(单例模式或静态单例模式) -> SqlSession
public class MybatisUtils {
// 提升作用域
private static SqlSessionFactory sqlSessionFactory = null;
static {
try {
// Mybatis使用第一步: 获取SqlSessionFactory对象
String resource = "mybatis-config.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
} catch (IOException e) {
e.printStackTrace();
}
}
//既然有了 SqlSessionFactory,顾名思义,我们可以从中获得 SqlSession 的实例。
// SqlSession 提供了在数据库执行 SQL 命令所需的所有方法。你可以通过 SqlSession 实例来直接执行已映射的 SQL 语句。
public static SqlSession getSqlSession(){
return sqlSessionFactory.openSession();
}
}
// 书写顺序:
1.配置maven的pom.xml -> 2.写工具类MybatisUtils -> 3.添加2中需要的mybatis-config.xml -> 4.添加实体类,对应接口和Mapper文件 -> 5.写测试类进行测试.
1.2 xml文件配置
<!-- 1.配置mybatis-config.xml -->
<!-- 1.1 配置属性讲解:
configuration(配置)
properties(属性)
settings(设置)
typeAliases(类型别名)
typeHandlers(类型处理器)
objectFactory(对象工厂)
plugins(插件)
environments(环境配置)
environment(环境变量)
transactionManager(事务管理器)
dataSource(数据源)
databaseIdProvider(数据库厂商标识)
mappers(映射器)
(注意元素节点的顺序!顺序不对会报错)
-->
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
<!--configuration:核心配置文件-->
<configuration>
<!--<environments default="选择默认的环境">:配置多套环境 -->
<environments default="development">
<environment id="development">
<!-- <transactionManager type="JDBC"/> :事务管理器(有JDBC和MANAGED) -->
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<property name="driver" value="com.mysql.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3305/mybatis?useSSL=true&useUnicode=true&characterEncoding=UTF-8"/>
<property name="username" value="root"/>
<property name="password" value="root"/>
</dataSource>
</environment>
</environments>
<!-- 每一个Mapper.xml都需要在Mybatis核心配置文件中注册!
[没注册会出现的错误]: org.apache.ibatis.binding.BindingException: Type interface com.kuang.dao.UserMapper is not known to the MapperRegistry.-->
<mappers>
<!-- 1.resource: xml可以放在任意位置; -->
<mapper resource="com/kuang/dao/UserMapper.xml"></mapper>
<!-- 2.class: [使用映射器接口实现类的完全限定类名,需要配置文件名称和接口名称一致,并且位于同一目录下] -->
<!-- <mapper class="com.kuang.dao.UserMapper"/> -->
<!-- <package name="com.kuang.dao"/> -->
</mappers>
</configuration>
------------------------------------------------------------------------------------
<!-- 2.配置Mapper文件 -->
<!-- 2.1 属性讲解
id: Mapper接口中的方法;
parameterType: 参数类型;
resultType: 返回值类型(当数据库字段与实体类字段一致时使用);
resultMap: 结果集映射(当数据库字段与实体类字段 不一致 时使用) -->
<?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.kuang.dao.UserMapper">
<!-- 查全部 -->
<select id="getUserList" resultType="com.kuang.pojo.User">
select * from mybatis.user
</select>
<!-- 根据id查 -->
<select id="getUserById" parameterType="int" resultType="com.kuang.pojo.User">
select * from mybatis.user where id = #{id}
</select>
<!-- 插入 -->
<!-- #{id}: 对象中的属性可以直接取出来. -->
<insert id="addUser" parameterType="com.kuang.pojo.User">
insert into mybatis.user (id, name, pwd)
values (#{id},#{name},#{pwd})
</insert>
<!-- 修改 -->
<update id="updateUser" parameterType="com.kuang.pojo.User">
update mybatis.user
set name=#{name},pwd=#{pwd}
where id=#{id}
</update>
<!-- 删除 -->
<delete id="deleteUser" parameterType="int">
delete from mybatis.user where id=#{id}
</delete>
</mapper>
<!-- 2.1.1 resultMap的使用: 结果集映射 -->
<resultMap id="UserMap" type="User">
<!-- column对应数据库中的字段, property对应实体类的属性 -->
<result column="pwd" property="password"/>
</resultMap>
<select id="getUserById" parameterType="int" resultMap="UserMap">
select * from mybatis.user where id = #{id}
</select>
1.3 日志工厂配置
1.LOG4J
// 1.1 步骤一: 导入log4j的包
<!-- https://mvnrepository.com/artifact/log4j/log4j -->
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.17</version>
</dependency>
// 1.2 步骤二: 配置log4j.properties
#将等级为DEBUG的日志信息输出到console 和 file这两个目的地,console和file的定义在下面的代码
log4j.rootLogger=DEBUG,console,file
#控制台输出的相关设置
log4j.appender.console = org.apache.log4j.ConsoleAppender
log4j.appender.console.Target = System.out
log4j.appender.console.Threshold=DEBUG
log4j.appender.console.layout = org.apache.log4j.PatternLayout
log4j.appender.console.layout.ConversionPattern=[%c]-%m%n
#文件输出的相关设置
log4j.appender.file = org.apache.log4j.RollingFileAppender
log4j.appender.file.File=./log/kuang.log
log4j.appender.file.MaxFileSize=10mb
log4j.appender.file.Threshold=DEBUG
log4j.appender.file.layout=org.apache.log4j.PatternLayout
log4j.appender.file.layout.ConversionPattern=[%p][sheepCode%d{yy-MM-dd}][%c]%m%n
#日志输出级别
log4j.logger.org.mybatis=DEBUG
log4j.logger.java.sql=DEBUG
log4j.logger.java.sql.Statement=DEBUG
log4j.logger.java.sql.ResultSet=DEBUG
log4j.logger.java.sql.PreparedStatement=DEBUG
// 1.3 步骤3: mybatis-config.xml中配置
<!-- 配置日志工厂 -->
<settings>
<setting name="logImpl" value="LOG4J"/>
</settings>
// 1.4 步骤4: 使用且测试.
1.4 分页
//1.分页实现
<!-- 分页实现查询 -->
<select id="getUserByLimit" parameterType="map" resultMap="UserMap">
select * from mybatis.user limit #{startIndex},#{pageSize}
</select>
@Test
public void getUserByLimit() {
SqlSession sqlSession = MybatisUtils.getSqlSession();
UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
Map<String, Integer> map = new HashMap<String, Integer>();
map.put("startIndex",2);
map.put("pageSize",2);
List<User> userList = userMapper.getUserByLimit(map);
for (User u: userList) {
System.out.println(u);
}
sqlSession.close();
}
//2.分页插件: Mybatis-PageHelper
1.5 注解开发
#{} 和 ${} 的区别:
1.#{}用来传入参数, sql在解析的时候会加上" ", 当成字符串来解析;
2.#{}能够很大程度地防止sql注入;
注意: ${}传入数据会直接显示在生成的sql中, 无法防止SQL注入; $一般用于传入数据库对象, 比如数据库表名; [能用#{}则尽量使用#{}]
@Param("xxName"): 如果方法存在多个参数且是基本类型则必须把这个注解加上, 引用类型则不用加;[只有一个基本类型时可忽略,但建议加上;]
@Select("sql语句")
@Insert("sql语句")
@Update("sql语句")
@Delete("sql语句")
//步骤: 1.写接口+注解; -> 2.在mybatis-config.xml中注册; -> 3.写测试用例
//1...
public interface UserMapper {
@Select("select * from mybatis.user where id = #{id}")
User getUserById(@Param("id") int id);
@Insert("insert into mybatis.user (id,name,pwd) values(#{id},#{name},#{password})")
int addUser(User user);
@Update("update mybatis.user set name=#{name},pwd=#{password} where id=#{id}")
int updateUser(User user);
@Delete("delete from mybatis.user where id=#{uid}")
int deleteUser(@Param("uid") int id);//使用的是uid
}
//2...
<!-- 绑定接口 -->
<mappers>
<mapper class="com.kuang.dao.UserMapper"/>
</mappers>
//3.测试:略
1.6 一对多和多对一
小结:
1. 关联-association
2. 集合-collection
3. 所以association是用于一对一和多对一,而collection是用于一对多的关系;
4. JavaType和ofType都是用来指定对象类型的;
JavaType: 是用来指定pojo中属性的类型;
ofType: 指定的是映射到list集合属性中pojo的类型;
//------------------------------------------------------------------------------
//多对一的情况:
public class Student {
private int id;
private String name;
//多对一: 学生需要关联一个老师
private Teacher teacher;//[复杂类型]
}
//StudentMapper.xml
<!-- 方法:按照结果嵌套处理 -->
<select id="getStudent2" resultMap="StudentTeacher2">
select s.id sid, s.name sname, t.name tname
from mybatis.student s, mybatis.teacher t
where s.tid = t.id
</select>
<resultMap id="StudentTeacher2" type="Student">
<result property="id" column="sid"/>
<result property="name" column="sname"/>
<association property="teacher" javaType="Teacher">
<result property="name" column="tname"/>
</association>
</resultMap>
//--------------------------------------------------------------------------------
//一对多的情况:
public class Teacher {
private int id;
private String name;
//一对多: 一个老师拥有多个学生;
private List<Student> students;
}
//TeacherMapper.xml
<!-- 方法: 按照结果嵌套查询 [推荐使用, 更注重写sql] -->
<select id="getTeacher" resultMap="TeacherStudent">
select s.id sid, s.name sname, t.name tname, t.id tid
from mybatis.student s, mybatis.teacher t
where s.tid =t.id and t.id = #{tid}
</select>
<resultMap id="TeacherStudent" type="Teacher">
<result property="id" column="tid"/>
<result property="name" column="tname"/>
<!-- 复杂的属性,我们需要单独处理 对象: association 集合: collection
javaType="": 指定属性的类型
集合中的泛型信息,我们使用ofType获取 -->
<collection property="students" ofType="Student">
<result property="id" column="sid"/>
<result property="name" column="sname"/>
<result property="tid" column="tid"/>
</collection>
</resultMap>
1.7 动态SQL⭐⭐⭐
if
choose (when, otherwise)
trim(where, set)
foreach
//BlogMapper.xml
<!-- sql片段抽取 -->
<sql id="if-title-author">
<if test="title != null">
title = #{title}
</if>
<if test="author != null">
and author = #{author}
</if>
</sql>
<!-- 1.if 和 where语句 -->
<select id="queryBlogIF" parameterType="map" resultType="blog">
select * from mybatis.blog
<where>
<!-- 引用sql片段 -->
<include refid="if-title-author"/>
</where>
</select>
<!-- 2.choose语句(choose + when + otherwise), 语法类似Java中的switch语法 -->
<select id="queryBlogChoose" parameterType="map" resultType="blog">
select * from mybatis.blog
<where>
<choose>
<when test="title != null">
title = #{title}
</when>
<when test="author != null">
and author = #{author}
</when>
<otherwise>
and views = #{views}
</otherwise>
</choose>
</where>
</select>
<!-- 3.set语句 -->
<update id="updateBlog" parameterType="map">
update mybatis.blog
<set>
<if test="title != null">
title = #{title},
</if>
<if test="author != null">
author = #{author},
</if>
<if test="views != null">
views = #{views}
</if>
</set>
<where>
id = #{id}
</where>
</update>
<!-- 4.foreach语句 -->
<!-- select * from mybatis.blog where 1=1 and (id=1 or id=2 or id=3)
我们现在传递一个万能的map,这map中可以存在一个集合! -->
<select id="queryBlogForeach" parameterType="map" resultType="blog">
select * from mybatis.blog
<where>
<foreach collection="ids" item="id" open="and (" separator="or" close=")">
id = #{id}
</foreach>
</where>
</select>
1.8 Mybatis缓存 [了解]
11.1、简介
1. 什么是缓存 [ Cache ]?
存在内存中的临时数据。
将用户经常查询的数据放在缓存(内存)中,用户去查询数据就不用从磁盘上(关系型数据库
数据文件)查询,从缓存中查询,从而提高查询效率,解决了高并发系统的性能问题。
2. 为什么使用缓存?
减少和数据库的交互次数,减少系统开销,提高系统效率。
3. 什么样的数据能使用缓存?
经常查询并且不经常改变的数据。
11.2、Mybatis缓存
MyBatis包含一个非常强大的查询缓存特性,它可以非常方便地定制和配置缓存。缓存可以极大的
提升查询效率。
MyBatis系统中默认定义了两级缓存:一级缓存和二级缓存
默认情况下,只有一级缓存开启。(SqlSession级别的缓存,也称为本地缓存)
二级缓存需要手动开启和配置,他是基于namespace级别的缓存。
为了提高扩展性,MyBatis定义了缓存接口Cache。我们可以通过实现Cache接口来自定义二
级缓存
11.3、一级缓存
一级缓存也叫本地缓存:
与数据库同一次会话期间查询到的数据会放在本地缓存中。
以后如果需要获取相同的数据,直接从缓存中拿,没必须再去查询数据库
11.4、二级缓存
二级缓存也叫全局缓存,一级缓存作用域太低了,所以诞生了二级缓存
基于namespace级别的缓存,一个名称空间,对应一个二级缓存;
工作机制
一个会话查询一条数据,这个数据就会被放在当前会话的一级缓存中;
如果当前会话关闭了,这个会话对应的一级缓存就没了;但是我们想要的是,会话关闭了,一
级缓存中的数据被保存到二级缓存中;
新的会话查询信息,就可以从二级缓存中获取内容;
不同的mapper查出的数据会放在自己对应的缓存(map)中;
解决遇到的常见问题:
1.[错误描述一]:
maven静态资源过滤问题, 因为约定大于配置 -> 导致问题: maven由于他的约定大于配置, 我们之后可能遇到我们的配置文件, 无法被导出或者生效的问题.
[解决方案如下]:
//<!-- 在build中配置resources,来防止我们资源导出失败的问题 -->
<build>
<resources>
<resource>
<directory>src/main/resources</directory>
<includes>
<include>**/*.properties</include>
<include>**/*.xml</include>
</includes>
<filtering>true</filtering>
</resource>
<resource>
<directory>src/main/java</directory>
<includes>
<include>**/*.properties</include>
<include>**/*.xml</include>
</includes>
<filtering>true</filtering>
</resource>
</resources>
</build>
----------------------------------------------------------------------------------
2.[错误描述二]:
### Error building SqlSession.
### Cause: org.apache.ibatis.builder.BuilderException: Error creating document instance. Cause: com.sun.org.apache.xerces.internal.impl.io.MalformedByteSequenceException: 1 字节的 UTF-8 序列的字节 1 无效。
[解决方案一]:
将mybatis-config.xml的开头中"UTF-8"改为"UTF8" -> <?xml version="1.0" encoding="UTF8" ?>
[解决方案二]:
(这个问题其实是因为你把IDEA的File Encodings的方式设置为了GBK)
更改IDEA设置-> settings -> Editor -> FileEncoding -> 把Global 和 Project Encoding都设置为UTF-8即可.
----------------------------------------------------------------------------------
3.添加自定义文件模板:
3.1、file—setting,左上角输入template,
3.2、在左侧栏找到File And Code Templates
3.3、中间选中Files
3.4、点击+号,添加模板
3.5、输入模板名字:Name:mybatis-config.xml (name可以自定义)
3.6、后缀名extension:xml
3.7、在面板中间输入内容.
二.MyBatisPlus [扩展]
2.1 简介
1.MyBatis-Plus (opens new window)(简称 MP)是一个 MyBatis (opens new window)的增强工具,在 MyBatis 的基础上只做增强不做改变,为简化开发、提高效率而生。
2.MyBatisPlus手册: https://baomidou.com/
类似Jpa, tk-mapper, MyBatisPlus;
2.2 测试MybatisPlus
- 建表
-- 1.创建数据库表`mybatis_plus`
DROP TABLE IF EXISTS user;
CREATE TABLE user
(
id BIGINT(20) NOT NULL COMMENT '主键ID',
name VARCHAR(30) NULL DEFAULT NULL COMMENT '姓名',
age INT(11) NULL DEFAULT NULL COMMENT '年龄',
email VARCHAR(50) NULL DEFAULT NULL COMMENT '邮箱',
PRIMARY KEY (id)
);
DELETE FROM user;
INSERT INTO user (id, name, age, email) VALUES
(1, 'Jone', 18, 'test1@baomidou.com'),
(2, 'Jack', 20, 'test2@baomidou.com'),
(3, 'Tom', 28, 'test3@baomidou.com'),
(4, 'Sandy', 21, 'test4@baomidou.com'),
(5, 'Billie', 24, 'test5@baomidou.com');
-
自动填充和必须字段
1.创建时间和更新时间 国际标准: gmt_create, update_time; 自定义: create_time, update_time; 2.
-
逻辑删除
物理删除:从数据库中直接移除;
逻辑删除:在数据库表中没有被移除,而是通过一个变量来让他失效!deleted=0 => deleted=1;
1.//逻辑删除组件 @Bean public ISqlInjector sqlInjector(){ return new LogicSqlInjector(); } 2.# 配置逻辑删除_已删除=1,未删除=0 mybatis-plus.global-config.db-config.logic-delete-value=1 mybatis-plus.global-config.db-config.logic-not-delete-value=0 结果_删除语句变为更新操作: ==> Preparing: UPDATE user SET deleted=1 WHERE id=? AND deleted=0 ==> Parameters: 1(Long) <== Updates: 1
-
wrapper条件构造器
@SpringBootTest
public class WrapperTest {
@Autowired
private UserMapper userMapper;
//条件查询1:
@Test
void contextLoads() {
// 查询name不为空,并且邮箱不为空的用户,年龄大于等于12 的用户
QueryWrapper<User> wrapper = new QueryWrapper<>();
wrapper
.isNotNull("name")
.isNotNull("email")
.ge("age",12);
userMapper.selectList(wrapper).forEach(System.out::println);//和我们刚才学习的map对比一下
}
//条件查询2:
@Test
void test2() {
//查询"名字=狂神说"
QueryWrapper<User> wrapper = new QueryWrapper<>();
wrapper.eq("name","狂神说");
User user = userMapper.selectOne(wrapper);//查询一个数据,出现多个结果使用List 或 Map
System.out.println(user);
}
//条件查询3:
@Test
void test3() {
//查询 "年龄在 20-30 岁" 之间的用户
QueryWrapper<User> wrapper = new QueryWrapper<>();
wrapper.between("age",20,30); // 区间
Integer count = userMapper.selectCount(wrapper);//查询结果数
System.out.println("查询数量=" + count);
}
//模糊查询4:
@Test
void test4() {
//查询年龄在 20 - 30 岁之间的用户
QueryWrapper<User> wrapper = new QueryWrapper<>();
wrapper
// 名字中不包含"e"
.notLike("name","e")
//左和右匹配—— t%
.likeRight("email","t");
List<Map<String, Object>> maps = userMapper.selectMaps(wrapper);
maps.forEach(System.out::println);
}
//条件查询5——子查询:
@Test
void test5() {
QueryWrapper<User> wrapper = new QueryWrapper<>();
//条件_id在子查询中查出来!
wrapper.inSql("id","select id from user where id < 3");
List<Object> objects = userMapper.selectObjs(wrapper);
objects.forEach(System.out::println);
}
//条件查询6
@Test
void test6() {
QueryWrapper<User> wrapper = new QueryWrapper<>();
//通过id进行排序
wrapper.orderByDesc("id");
List<User> users = userMapper.selectList(wrapper);
users.forEach(System.out::println);
}
}
2.3 代码自动生成器:
//代码自动生成器_ (案例,此处不可用!)
public class KuangCode {
public static void main(String[] args) {
// 需要构建一个 代码自动生成器 对象
AutoGenerator mpg = new AutoGenerator();
// 配置策略
// 1、全局配置
GlobalConfig gc = new GlobalConfig();
String projectPath = System.getProperty("user.dir");
gc.setOutputDir(projectPath+"/src/main/java");
gc.setAuthor("狂神说");
gc.setOpen(false);
gc.setFileOverride(false); // 是否覆盖
gc.setServiceName("%sService"); // 去Service的I前缀
gc.setIdType(IdType.ID_WORKER);
gc.setDateType(DateType.ONLY_DATE);
gc.setSwagger2(true);
mpg.setGlobalConfig(gc);
//2、设置数据源
DataSourceConfig dsc = new DataSourceConfig();
dsc.setUrl("jdbc:mysql://localhost:3306/kuang_community?useSSL=false&useUnicode=true&characterEncoding=utf-8&serverTimezone=GMT%2B8");
dsc.setDriverName("com.mysql.cj.jdbc.Driver");
dsc.setUsername("root");
dsc.setPassword("123456");
dsc.setDbType(DbType.MYSQL);
mpg.setDataSource(dsc);
//3、包的配置
PackageConfig pc = new PackageConfig();
pc.setModuleName("blog"); //模块名
pc.setParent("com.kuang");//父包名
pc.setEntity("entity"); //子包名
pc.setMapper("mapper");
pc.setService("service");
pc.setController("controller");
mpg.setPackageInfo(pc);
//4、策略配置
StrategyConfig strategy = new StrategyConfig();
//!!!此处需要自定义!!!
strategy.setInclude("blog_tags","course","links","sys_settings","user_record","user_say"); // 设置要映射的表名(!!!)
strategy.setNaming(NamingStrategy.underline_to_camel);
strategy.setColumnNaming(NamingStrategy.underline_to_camel);
strategy.setEntityLombokModel(true); // 自动生成_lombok;
strategy.setLogicDeleteFieldName("deleted");//逻辑删除
// 自动填充配置
TableFill gmtCreate = new TableFill("gmt_create", FieldFill.INSERT);
TableFill gmtModified = new TableFill("gmt_modified", FieldFill.INSERT_UPDATE);
ArrayList<TableFill> tableFills = new ArrayList<>();
tableFills.add(gmtCreate);
tableFills.add(gmtModified);
strategy.setTableFillList(tableFills);
// 乐观锁
strategy.setVersionFieldName("version");
strategy.setRestControllerStyle(true);
strategy.setControllerMappingHyphenStyle(true); //localhost:8080/hello_id_2
mpg.setStrategy(strategy);
mpg.execute(); //执行
}
}