本篇分别讲解了Mybatis、SpringMVC、Spring,最后又将三者整合,成就SSM框架
每个内容都是比较细的,适用于新手,也适用于会写代码但不知道SSM每一部分如何整合的同学。
本文需要一些初始代码和sql(比如一套UserInfo实体类和user_info数据库以及对应的Servlet代码),这部分很少,大家用自己原有的JavaWeb项目也可以。如有需要留言邮箱即可,我看到会发过去
看完本篇你可以了解到什么:
- JavaWeb变成SSM的过程
- SSM每一部分的含义
- SSM所有配置,SSM项目建立
本文内容很不错,值得一看。有不了解的地方可以留言
文章目录
一、JavaWeb项目
1. 创建项目
使用maven骨架创建,选择webapp,如图所示
在创建完成后,会有从仓库中拉取web骨架,需要等一段时间,注意maven已经是否已经配置了国内的数据源。
完成后是这样的:
这样没有java文件夹,右击新建文件夹,会有提示直接将java文件夹创建出来。
2. 添加依赖
<!-- servlet-api -->
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>4.0.1</version>
</dependency>
<!-- 数据库jar -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.27</version>
</dependency>
<!-- jsp标签库 -->
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>jstl</artifactId>
<version>1.2</version>
</dependency>
<!--单元测试的依赖-->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
</dependency>
3. 配置tomcat
不赘述
4. 启动EL表达式配置
使用骨架创建的web项目是不能用EL表达式的
原因:idea给我们提供的代码模板默认使用的2.3版本,可以修改版本,也可以在jsp中添加代码,如下所示
解决方案选择其一
1.在jsp页面中增加
<%@ page isELIgnored="false"%>
2.修改web.xml
<!DOCTYPE web-app PUBLIC
"-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
"http://java.sun.com/dtd/web-app_2_3.dtd" >
<web-app
xmlns="http://xmlns.jcp.org/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee
http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd" version="3.1">
</web-app>
5. 配置lombok
5.1 添加依赖
<!-- lombok -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.26</version>
<scope>provided</scope>
</dependency>
5.2 设置idea支持lombok
开启编译注解功能
5.3 常用注解
@Setter/@Setter :注解在属性上;为属性提供 setter/getter 方法
@ToString:为类生成 tostring 方法,输出类名和所有属性
@EqualsAndHashCode:生成 equal 和 hashCode 方法,可以通过参数,排除一些属性
@NoArgsConstructor :注解在类上;为类提供一个无参的构造方法
@RequiredArgsConstructor:会生成一个包含常量(final 修饰),和标识了 NotNull 的变量的构造方
法 。
@AllArgsConstructor :注解在类上;为类提供一个全参的构造方法
@Data ;注解在类上相当于 @Getter、@Setter、@ToString 等几个注解的合集。
@Log4j :注解在类上,为类提供一个属性名为 log 的 log4j 日志对象
二、Mybatis
1. 前言、理论
1.1 原生CRUD的问题
-
编码过于繁琐
-
结果集映射成对象需要我们自己编写代码
-
性能一般
-
Sql语句与Java代码高度耦合
1.2 ORM
Object-Relationl Mapping,对象关系映射,它的作用是在关系型数据库和对象之间作一个映射,这样我们在具体的操作数据库的时候,只要像平时操作对象一样操作它就可以了,ORM框架会根据映射完成对数据库的操作,就不需要再去和复杂的SQL语句打交道了。
Hibernate
是全自动ORM框架
自动进行对象和结果集的转换;sql自动生成和执行 hql
重量级;更新慢
Mybatis
是半自动ORM框架
开发人员手动写sql
轻量级;更新快;灵活性好
1.3 介绍
MyBatis 本是Apache的一个开源项目iBatis, 2010年这个项目由Apache Software Foundation 迁移到了Google Code,且改名为MyBatis 。2013年11月迁移到GitHub。iBATIS一词来源于“internet”和“abatis”的组合,是一个基于Java的持久层框架。
MyBatis 是一款优秀的持久层框架,它支持定制化 SQL、存储过程以及高级映射。MyBatis 避免了几乎所有的 JDBC 代码和手动设置参数以及获取结果集。MyBatis 可以使用简单的 XML 或注解来配置和映射原生信息,将接口和 Java 的 POJOs(Plain Ordinary Java Object,普通的 Java对象)映射成数据库中的记录。
官网:https://mybatis.org/mybatis-3/zh/index.html
2. 添加依赖
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.5.11</version>
</dependency>
3. 配置文件
3.1 核心文件配置
在resourdces目录下新建:sqlMapConfig.xml,如下图所示
内容可在官网处查看,或下面代码,官网:https://mybatis.org/mybatis-3/zh/getting-started.html
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"https://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
<environments default="development">
<environment id="development">
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<property name="driver" value="${jdbc_driver}"/>
<property name="url" value="${jdbc_url}"/>
<property name="username" value="${jdbc_username}"/>
<property name="password" value="${jdbc_password}"/>
</dataSource>
</environment>
</environments>
<mappers>
<mapper resource="org/mybatis/example/BlogMapper.xml"/> 一会此处需要修改
</mappers>
</configuration>
一会需要添加两个标签:
1.properties 配置文件,存放配置参数
2.typeAliases 自动扫描beans包,自动将类起别名,别名是:类名首字母小写
1. properties标签
可以直接在在value中配置,也可以单独放到一个properties文件中,若希望将配置数据放到jdbc.properties
在xml中添加
<properties resource="jdbc.properties"></properties>
如图所示
jdbc.properties
文件内容:
jdbc_driver=com.mysql.cj.jdbc.Driver
jdbc_url=jdbc:mysql://127.0.0.1:3306/数据库名?useSSL=false&useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai
jdbc_username=root
jdbc_password=root
2. typeAliases标签
大概的意义是,如果不加typeAliases
,那么在映射文件中需要将实体类的地址写全,也就是com.qst.binzhou2022.beans.UserInfo
加上之后,只需要写:userInfo
就知道对应的实体类是哪一个
具体作用很快就描述
<typeAliases>
<package name="com.qst.binzhou2022.beans"/>
</typeAliases>
3.2 映射文件配置
1. 新建bean对应的xml
在resources下新建相同的包名创建一个与user_info表对应的xml映射文件;
注意:
- 路径必须和beans一致
- resources中创建时一级一级新建文件夹,不要直接用
com.qst.binzhou2022
这样,resource下会将.
作为文件名的一部分
UserInfoMapper.xml内容: 摘至官网
(后面再自己写需要的内容)
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"https://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="org.mybatis.example.BlogMapper">
<select id="selectBlog" resultType="Blog">
select * from Blog where id = #{id}
</select>
</mapper>
注意映射文件的命名空间:
<mapper namespace="org.mybatis.example.BlogMapper">
在传统模式开发下,命名空间写什么都可以,没有用,如果觉得爆红不好看可以写成beans的目录,idea就不会爆红了(但注意,实际没有其他用处)
<mapper namespace="com.qst.binzhou2022.beans.UserInfo">
具体应该写什么在 基于接口代理模式开发中说明
2. 核心文件配置
刚刚的核心的文件是这样的:
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"https://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
<environments default="development">
<environment id="development">
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<property name="driver" value="${jdbc_driver}"/>
<property name="url" value="${jdbc_url}"/>
<property name="username" value="${jdbc_username}"/>
<property name="password" value="${jdbc_password}"/>
</dataSource>
</environment>
</environments>
<mappers>
<mapper resource="org/mybatis/example/BlogMapper.xml"/>
</mappers>
</configuration>
需要修改:
对应刚刚新建的xml
<mappers>
<mapper resource="com/qst/binzhou2022/mapper/UserInfoMapper.xml"/>
</mappers>
4. 初试sql
4.1 映射文件
再映射文件中编写查询sql
<mapper namespace="com.qst.binzhou2022.beans.UserInfo">
<select id="findUserInfoAll" resultType="com.qst.binzhou2022.beans.UserInfo"> 注意此处
select * from user_info
</select>
</mapper>
由于我们之前配置了typeAliases
标签,可以这样写,resultType
代表返回类型
<mapper namespace="com.qst.binzhou2022.beans.UserInfo">
<select id="findUserInfoAll" resultType="userInfo"> 注意此处
select * from user_info
</select>
</mapper>
4.2 Dao层代码
public List<UserInfo> getUserInfo(UserInfo userInfo) throws SQLException {
boolean flag = false;
// 获取主配置文件
String resource = "sqlMapConfig.xml";
InputStream inputStream = null;
try {
inputStream = Resources.getResourceAsStream(resource);
} catch (IOException e) {
e.printStackTrace();
}
// 由建造者模式建筑一个工厂里类
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
// 由工厂类建造一个产品
SqlSession sqlSession = sqlSessionFactory.openSession();
// 以上完全可以写一个工具类,可复用代码--单例模式
// sqlSession有很多方法,因为xml中写的是查询多条
/* 这里怎么知道findUserInfoAll呢?
1.加载了主配置文件
2.主配置文件的mappers标签中写明了映射文件的地址
*/
List <UserInfo> userInfoList = sqlSession.selectList("findUserInfoAll");
for (UserInfo info : userInfoList) {
System.out.println(info);
}
return userInfoList;
}
5. 数据映射问题
目前为止,是可以查出来数据了,但是又有些问题,运行此时的代码,会发现查出来的数据全是null
原因:虽然从数据库中查询到了数据,但是数据的列名是这样的:
user_id user_name user_sex user_pass
而我们定义了resultType
中返回的是User实体类,实体类的属性是这样的:
userId userName userSex userPass
Mybatis并不能自动的将user_id
映射成userId
解决思路:主要是让Mybatis知道user_id
到底是哪一个数据,它该放到哪里,那么需要我们制定映射方案
解决方案1:为列起一个别名,让查出来的user_id
改名为userId
<select id="getUserInfo" resultType="userInfo">
select
user_id userId,user_name userName,user_age userAge, user_sex userSex, user_pass userPass
from user_info
</select>
备注:通过使用 SQL,可以为表名称或列名称指定别名。
SELECT column_name AS alias_name FROM table_name;
as可省略
解决方案2:我们来指定一个映射方案
写一个resultMap
标签 设置id
和type
属性,在内部编写映射方案
子元素说明:
- id元素 ,用于设置主键字段与实体类属性的映射关系
- result元素 ,用于设置普通字段与实体类属性的映射关系
<resultMap id="userInfoMap" type="userInfo">
<id column="user_id" property="userId"></id>
<result column="user_pass" property="userPass"></result>
<result column="user_name" property="userName"></result>
<result column="user_age" property="userAge"></result>
<result column="user_sex" property="userSex"></result>
</resultMap>
6. 日志管理Log4j
在开发过程中我们很多时候需要对sql进行调试,查看参数或者查看最后的sql什么样子,之前我们可以使用debug或者使用在控制台打印的方式查看,但是现在我们放到了xml中??怎么跟进sql??
需要借助日志工具Log4j
添加依赖:
<!-- log4j2 -->
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-core</artifactId>
<version>2.20.0</version>
</dependency>
在resources
下添加log4j2.xml
具体log4j2的配置可自行了解
<?xml version="1.0" encoding="UTF-8"?>
<Configuration status="DEBUG">
<Appenders>
<Console name="Console" target="SYSTEM_ERR">
<PatternLayout pattern="%d{YYYY-MM-dd HH:mm:ss} [%t] %-5p %c{1}:%L - %msg%n" />
</Console>
<RollingFile name="RollingFile" filename="log/qst.log"
filepattern="${logPath}/%d{YYYYMMddHHmmss}-fargo.log">
<PatternLayout pattern="%d{YYYY-MM-dd HH:mm:ss} [%t] %-5p %c{1}:%L - %msg%n" />
<Policies>
<SizeBasedTriggeringPolicy size="10 MB" />
</Policies>
<DefaultRolloverStrategy max="20" />
</RollingFile>
</Appenders>
<!-- DEBUG级别 Console控制台 RollingFile文件 -->
<Loggers>
<Root level="DEBUG">
<AppenderRef ref="Console" />
<AppenderRef ref="RollingFile" />
</Root>
</Loggers>
</Configuration>
7. 传统模式开发
普通模式,也称为传统DAO模式,就是在传统DAO模式下,定义接口和实现类,如 interface EmpDao class EmpDaoImpl implements EmpDao. 在实现类中,用SQLSession对象调用 select insert delete update 等方法实现.目前极为少见.在传统模式下,我们需要知道SqlSession对象 实现CURD和 参数传递的处理
7.1 返回(单个\多个\map)
1.1 返回单个 selectOne
修改映射文件:
<resultMap id="userInfoMap" type="userInfo">
<id column="user_id" property="userId"></id>
<result column="user_pass" property="userPass"></result>
<result column="user_name" property="userName"></result>
<result column="user_age" property="userAge"></result>
<result column="user_sex" property="userSex"></result>
</resultMap>
<!--查询单个对象-->
<select id="findUserInfoSingle" resultType="userInfoMap">
select *
from user_info where user_id = 1
</select>
修改dao代码:
List<UserInfo> userInfoList = new ArrayList<>();
UserInfo userInfoResult = sqlSession.selectOne("findUserInfoSingle");
userInfoList.add(userInfoResult);
userInfoList.forEach(System.out::println);
1.2 返回对象List集合 selectList,在4.1与4.2中
1.3 返回对象Map集合
List<UserInfo> userInfoList = new ArrayList<>();
Map<Integer, UserInfo> map = sqlSession.selectMap("findUserInfoAllMap", "userId");
Set<Integer> userIds = map.keySet();
for (Integer s : userIds) {
System.out.println(s + " --- " + map.get(s));
}
注意:
1. map键值的类型
2. selectMap属性
(1)第一个是映射文件中查询标签的id
(2)第二个属性指定 键
<resultMap id="userInfoMap" type="userInfo">
<id column="user_id" property="userId"></id>
<result column="user_pass" property="userPass"></result>
<result column="user_name" property="userName"></result>
<result column="user_age" property="userAge"></result>
<result column="user_sex" property="userSex"></result>
</resultMap>
<select id="getUserInfoMap" resultType="map" resultMap="userInfoMap">
select
*
from user_info;
</select>
或者
<select id="getUserInfoMap" resultType="map">
select
user_id userId,user_name userName,user_age userAge, user_sex userSex, user_pass userPass
from user_info;
</select>
7.2 mybatis参数传递
2.1 传递单个值参数
parameterType 在有参数情况下也是可以省略不写 mybatis 可以根据实际情况自动判断;定义号参数后在标签内sql中可以使用 #{} 和 ${} 进行占位
并且在单个值传递的时候{}中值 随便写
#{} 代表mybatis底层使用的preparedStatment语句对象,参数使用?作为占位符处理 --- 常用
${} 代表mybatis底层使用Statment语句对象,参数是以字符串拼接的形式设置
<select id="getUserInfo" resultMap="userInfoMap" parameterType="int">
select
*
from user_info where user_id = #{userId};
</select>
userInfoList = sqlSession.selectList("getUserInfo", "1");
selectList支持第二个参数
2.2 传递多个参数使用Map
Map<String, String> rap = new HashMap<>();
rap.put("userId", "1");
rap.put("userName", "a");
userInfoList = sqlSession.selectList("getUserInfoByMap", rap);
selectList第二个参数放map
<!--查询多个对象 返回List 测试多个参数 -->
<select id="findUserInfoAll" resultType="userInfo" resultMap="userInfoMap" parameterType="map">
select * from user_info where user_sex = #{sex} and user_name = #{userName}
</select>
2.3 传递对象参数
userInfo.setUserId(1);
userInfo.setUserName("a");
UserInfo userInfo1 = sqlSession.selectOne("getUserInfoByBean", userInfo);
System.out.println(userInfo1.toString());
<select id="getUserInfoByBean" resultType="userInfo" resultMap="userInfoMap" parameterType="userInfo">
select *
from user_info where user_id = #{userId}
and user_name = #{userName}
</select>
Mybatis很灵活, 单个实体类不加parameterType
也可以 (单个值随便写)
注意:在xml中<
等符号有特殊含义,不能直接使用,需要转义
比如:
sql是没有问题的,但在xml中需要改成
<select id="getUserInfoMap" resultType="map" resultMap="userInfoMap">
select
*
from user_info
where user_age <= 10;
</select>
网站:https://www.w3school.com.cn/html/html_entities.asp
空格 | |||
---|---|---|---|
< | 小于号 | < | < |
> | 大于号 | > | > |
& | 和号 | & | & |
" | 引号 | " | " |
’ | 撇号 | ' (IE不支持) | ' |
¢ | 分(cent) | ¢ | ¢ |
£ | 镑(pound) | £ | £ |
¥ | 元(yen) | ¥ | ¥ |
€ | 欧元(euro) | € | € |
§ | 小节 | § | § |
© | 版权(copyright) | © | © |
® | 注册商标 | ® | ® |
™ | 商标 | ™ | ™ |
× | 乘号 | × | × |
÷ | 除号 | ÷ | ÷ |
7.3 insert时主键回填
在实验中我们发现主键是自增的,我们并没有传入主键,那我们如何获取到插入的数据的主键呢??
-
<insert id="insertUserInfo" parameterType="userInfo" useGeneratedKeys="true" keyColumn="user_id" keyProperty="userId"> insert into user_info (user_name, user_pass, user_sex, user_age) VALUES (#{userName}, #{userPass}, #{userSex}, #{userAge}) </insert> 添加几个参数useGeneratedKeys keyColumn keyProperty
-
<insert id="insertUserInfo" parameterType="userInfo"> <selectKey order="AFTER" keyProperty="userId" keyColumn="user_id" resultType="int"> select @@identity </selectKey> insert into user_info (user_name, user_pass, user_sex, user_age) VALUES (#{userName}, #{userPass}, #{userSex}, #{userAge}) </insert> 添加selectKey标签
8. 基于接口代理模式开发
8.1 修改代码、两模式对比
先解释再看下方步骤,舍弃dao
-
Mybatis要求使用mapper作为接口文件夹名称(保证了编译后在同一文件夹),且接口名需要和映射文件同名。
-
mapper文件夹下放接口,接口
方法名
和映射文件中的id名
相同 -
映射文件的命名空间
namespace
, 在3.2.1
中大概说明了命名空间,在基于接口代理模式开发时,需要指定mapper文件夹下的接口文件
下面是修改过程
在com.qst.binzhou2022目录下新增一个mapper包;此处要注意mapper包路径要跟resources下的mapper一致
在com.qst.binzhou2022.mapper下新增一个接口文件 命名为UserInfoMapper, 并且新增一个方法(方法名要与映射文件中的sql的id一致)
修改映射文件的命名空间
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-XaqEm9Xn-1678960789411)(D:\其他\桌面\毕设\JavaWeb项目.assets\image-20230302223000061.png)]
在service
层:修改代码
/*
Mybatis接口代理
*/
public boolean getUserInfo(UserInfo userInfo) throws SQLException, IOException {
//获取
String resource = "sqlMapConfig.xml";
InputStream inputStream = null;
try {
inputStream = Resources.getResourceAsStream(resource);
} catch (IOException e) {
e.printStackTrace();
}
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
SqlSession sqlSession = sqlSessionFactory.openSession();
// 通过getMapper方法获得UserInfoMapper接口的实现类的对象
// 具体是怎么实现的我们不需要管
UserInfoMapper userInfoMapper = sqlSession.getMapper(UserInfoMapper.class);
boolean flag = false;
userInfo = userInfoMapper.getUserInfoById(1); // 这个在8.2中
System.out.println(userInfo);
return flag;
}
接口模式开发总结
1. 接口的名字和Mapper映射文件名字必须保持一致
2. Mapper映射文件的namespace必须是接口的全限定类名
3. sql语句的id必须与接口对应方法的名称一致
4. 映射文件应该和接口编译之后放在同一个目录下
优点
1. 接口使各个模块之间更加规范
2. 接口中方法参数由开发人员制定,更加灵活
3. 通过代理模式由mybatis提供接口的实现类对象 无需编写实现类
8.2 参数
1. 单参数
<select id="getUserInfoById" resultMap="userInfoMap" parameterType="int">
select *
from user_info
where user_id = #{userId};
</select>
注:
实际上单参数怎么写都行,可以没有parameterType,#{}里面也可以随便写
mapper接口方法:
UserInfo getUserInfoById(int userId);
service层:
/*
Mybatis接口代理
*/
public boolean getUserInfo(UserInfo userInfo) throws SQLException, IOException {
//获取
String resource = "sqlMapConfig.xml";
InputStream inputStream = null;
try {
inputStream = Resources.getResourceAsStream(resource);
} catch (IOException e) {
e.printStackTrace();
}
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
SqlSession sqlSession = sqlSessionFactory.openSession();
// 通过getMapper方法获得UserInfoMapper接口的实现类的对象
// 具体是怎么实现的我们不需要管
UserInfoMapper userInfoMapper = sqlSession.getMapper(UserInfoMapper.class);
boolean flag = false;
userInfo = userInfoMapper.getUserInfoById(1); // 这个在8.2中
System.out.println(userInfo);
return flag;
}
2. 多参数
使用多参数出现的问题:
- 映射文件上面怎么写?一个参数可以用
parameterType
定义类型,多个怎么办 - 方法参数名和映射文件中怎么对应?
- mapper接口直接写上多个参数吗?
*解决方案一:可以使用arg (arg0 arg1 arg2 …) **
在mapper接口方法中正常写参数,参数名也正常起
而映射文件中对应的参数就是,arg (arg0 arg1 arg2 …)*
下标从0
开始
解决方案二:可以使用param(param1 param2 param3 …)*
同方案一,只是下标从1
开始
映射文件:
<select id="getUserInfoByIdAndName" resultMap="userInfoMap" >
select *
from user_info
where user_id = #{arg0} and user_name = #{arg1};
</select>
或
<select id="getUserInfoByIdAndName" resultMap="userInfoMap" >
select *
from user_info
where user_id = #{param1} and user_name = #{param2};
</select>
这两种是固定写法,Mybatis底层帮我们处理了,直接用,但是缺点很明显,可读性差
解决方案三:可以使用@Param注解标明参数
在mapper接口中:
UserInfo getUserInfoByIdAndName(@Param("user_id") int userId,@Param("user_name") String username);
映射文件:
<select id="getUserInfoByIdAndName" resultMap="userInfoMap" >
select *
from user_info
where user_id = #{user_id} and user_name = #{user_name};
</select>
这样一来,user_id
和userId
就可以对应上了
解决方案四:定义map和对象传入
map:与7.2.3
相同,#{}
中直接写map
定义的键就可以对应,如果参数多,这个方法也很好
对象:与7.2.4
相同,#{}
中直接写实体类的变量名
8.3 模糊查询
在上面的例子中我们使用的是= 如果我们希望用户名模糊查询呢?? 如果拼接字符串?
解决方案一:在传入参数的时候拼接%%
映射文件:
<select id="getUserLikeName" resultMap="userInfoMap">
select * from user_info
where user_name like #{userName};
</select>
接口:
List<UserInfo> getUserLikeName(int userId);
service:
List<UserInfo> list;
list = userInfoMapper.getUserLikeName("%"+"a"+"%");
list.forEach(System.out::println);
缺点很明显,需要在service中用字符串的形式拼接,也不利于可读性
解决方案二:在sql中使用concat函数拼接%%
<select id="getUserLikeName" resultMap="userInfoMap">
select *
from user_info
where user_name like concat("%", #{userName}, "%");
</select>
9. 动态SQL
一个表字段比较多的时候,根据不同的情况会需要不同的查询条件进行查询;需要我们写很多sql语句
MyBatis在简化操作方法提出了动态SQL功能,将使用Java代码拼接SQL语句,改变为在XML映射文件中使用特定标签拼接SQL语句。相比而言,大大减少了代码量,更灵活、高度可配置、利于后期维护。
9.1 IF标签
好处,此条SQL支持查询多种条件,如果我们需要根据name和pass查询,那么在userInfo
赋值时只给它这俩属性的值
那么,如果根据其他条件查询,同样可以使用这个
第一种:
<select id="getUserInfoSQL" resultMap="userInfoMap" parameterType="userInfo">
select * from user_info
where 1=1
<if test="userName!=null and userName != ''">
and user_name like concat("%", #{userName},"%")
</if>
<if test="userPass!=null and userPass != ''">
and user_pass=#{userPass}
</if>
<if test="userSex!=null and userSex != ''">
and user_sex=#{userSex}
</if>
<if test="userAge!=null and userAge != ''">
and user_age=#{userAge}
</if>
</select>
为什么有一个1=1?
根据条件,第一条判断userName可能为空,语句不执行,那么and如何放呢,第一条判断中加不加and呢,所以在最前面加一个1=1恒true,后面放心加and
第二种:去掉1=1,用框架内部的方法解决上述问题,添加where标签
<select id="getUserInfoSQL" resultMap="userInfoMap" parameterType="userInfo">
select * from user_info
<where>
<if test="userName!=null and userName != ''">
and user_name like concat("%", #{userName},"%")
</if>
<if test="userPass!=null and userPass != ''">
and user_pass=#{userPass}
</if>
<if test="userSex!=null and userSex != ''">
and user_sex=#{userSex}
</if>
<if test="userAge!=null and userAge != ''">
and user_age=#{userAge}
</if>
</where>
</select>
接口中的代码就不放了,和之前一样的操作。
9.2 choose when
IF条件直接换成when
service代码同IF
<select id="getUserInfoSQL" resultMap="userInfoMap" parameterType="userInfo">
select * from user_info
<where>
<choose>
<when test="userName!=null and userName != ''">
and user_name like concat("%", #{userName},"%")
</when>
<when test="userPass!=null and userPass != ''">
and user_pass=#{userPass}
</when>
<when test="userSex!=null and userSex != ''">
and user_sex=#{userSex}
</when>
<when test="userAge!=null and userAge != ''">
and user_age=#{userAge}
</when>
<otherwise>
and 1=1
</otherwise>
</choose>
</where>
</select>
与IF的区别
通过查看log,可以看出虽然在service中给userInfo定义了两个参数,但是在choose中只判断了一个,而IF中两个都进行了判断
即:choose when结构在找到满足条件拼接sql后余下的条件即便满足也不会拼接(只判断第一个条件)
9.3 set标签
service写法:
userInfo.setUserName("a");
userInfo.setUserSex("1");
userInfo.setUserId(2);
int i = userInfoMapper.updateUserById(userInfo);
sqlSession.commit();
记得要执行sqlSession.commit();否则数据不会更改
正常的写法:
<update id="updateUserById" parameterType="userInfo">
update user_info
set user_name = #{userName},
user_sex = #{userSex},
user_pass = #{userPass},
user_age = #{userAge}
where user_id = #{userId}
</update>
使用set标签
<update id="updateUserById" parameterType="userInfo">
update user_info
<set>
<if test="userName != null">
user_name = #{userName},
</if>
<if test="userSex != null">
user_sex = #{userSex},
</if>
<if test="userPass != null">
user_pass = #{userPass},
</if>
<if test="userAge != null">
user_age = #{userAge},
</if>
</set>
where user_id = #{userId}
</update>
可以发现执行时自动去掉了无用的,
9.4 trim标签
trim是用来动态拼接字符的一种特殊标签;set where都是一些特殊的trim
有一些属性
prefix 要增加的前缀
prefixOverrides 要去除的前缀
suffix 要增加的后缀
suffixOverrides 要去除的后缀
演示,如果要实现9.3
中的set
前缀增加set,后缀去掉,
<update id="updateUserById" parameterType="userInfo">
update user_info
<trim prefix="set" suffixOverrides=",">
<if test="userName != null">
user_name = #{userName},
</if>
<if test="userSex != null">
user_sex = #{userSex},
</if>
<if test="userPass != null">
user_pass = #{userPass},
</if>
<if test="userAge != null">
user_age = #{userAge},
</if>
</trim>
where user_id = #{userId}
</update>
那么演示执行的sql是:update user_info set user_name = 'a', user_sex = '1', user_age = 100 where user_id = 2
执行成功
如果改为prefixOverrides
呢,前缀去除,
<trim prefix="set" prefixOverrides=",">
结果是失败的:
可以发现sql
语句出了问题,where前面的,
没有去掉
其实如果是这样,是可以的:
需要根据具体,
放的位置,来定义用哪一个属性,对于set
标签,,
放前面后面都可以
<update id="updateUserById" parameterType="userInfo">
update user_info
<trim prefix="set" prefixOverrides=",">
<if test="userName != null">
,user_name = #{userName}
</if>
<if test="userSex != null">
,user_sex = #{userSex}
</if>
<if test="userPass != null">
,user_pass = #{userPass}
</if>
<if test="userAge != null">
,user_age = #{userAge}
</if>
</trim>
where user_id = #{userId}
</update>
9.5 foreach标签
写sql
时会遇到下面情况:
select * from user_info where user_name = 'a' or user_name = 'b' or user_name = 'c'
或
select * from user_info where user_name in ('a','b','c')
collection="" 遍历的集合或者是数组
参数是数组,collection中名字指定为array
参数是List集合,collection中名字指定为list
separator="" 多个元素取出的时候 用什么文字分隔
open="" 以什么开头
close="" 以什么结尾
item="" 中间变量名
service:
List<String> list = new ArrayList<>();
list.add("a");
list.add("b");
list.add("c");
List<UserInfo> userInfoList= userInfoMapper.getUserInfoForeach(list);
userInfoList.forEach(System.out::println);
接口:
List<UserInfo> getUserInfoForeach(List<String> list);
<select id="getUserInfoForeach" resultMap="userInfoMap" resultType="userInfo">
select * from user_info
where user_name in
<foreach collection="list" item="index" separator="," open="(" close=")">
#{index}
</foreach>
</select>
10. 一对一查询
业务需求是查询某个学生信息和学生所在班级信息
学生用StuInfo对象存储,班级用ClassInfo对象存储
学生和班级是一对一的关系
若通过查询学生且查询他班级的信息,结果如何保存呢
可以在StuInfo属性中加入ClassInfo对象,这样java对象方面的问题就解决了
Mybatis的问题:
之前虽然有resultMap映射,可以也仅是映射了StuInfo的属性,ClassInfo的属性怎么办呢
有解决方案,在内部加入association标签
好了,简单介绍完毕,开始准备工作:
准备工作1:创建相应的实体类
准备工作2:创建StuInfoMapper接口
准备工作3:创建映射文件
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"https://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.qst.binzhou2022.mapper.StuInfoMapper"> 注意这里
<resultMap id="stuInfoMap" type="stuInfo">
<id column="stu_id" property="stuId"></id>
<result column="stu_name" property="stuName"></result>
<result column="stu_code" property="stuCode"></result>
<result column="class_id" property="classId"></result>
<association property="classInfo" javaType="classInfo"> 注意这里
<id column="class_id" property="classId"></id>
<result column="class_name" property="className"></result>
<result column="class_grade" property="classGrade"></result>
</association>
</resultMap>
<select id="getStuInfo" resultMap="stuInfoMap">
select *
from stu_info
left join class_info ci on stu_info.class_id = ci.class_id
where stu_code = #{stuCode}
</select>
</mapper>
准备工作4:编写StuInfoService
可以发现,StuInfoService
和UserInfoService
中获取sqlSession
的代码是重复的,可以单独做一个工具类
package com.qst.binzhou2022.utils;
import lombok.Data;
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
import java.io.IOException;
import java.io.InputStream;
/**
* @author spengda
* @date 2022/4/51:34
*/
@Data
public class MybatisUtils {
SqlSessionFactory sqlSessionFactory;
private static MybatisUtils instance = new MybatisUtils();
private MybatisUtils (){
//获取
String resource = "sqlMapConfig.xml";
InputStream inputStream = null;
try {
inputStream = Resources.getResourceAsStream(resource);
} catch (IOException e) {
e.printStackTrace();
}
sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
}
public static MybatisUtils getInstance() {
return instance;
}
public SqlSession getSqlSession(){
SqlSession sqlSession = sqlSessionFactory.openSession();
return sqlSession;
}
public Object getMapper(Class classInfo){
return getSqlSession().getMapper(classInfo);
}
}
那么service:
package com.qst.binzhou2022.services;
import com.qst.binzhou2022.beans.StuInfo;
import com.qst.binzhou2022.beans.UserInfo;
import com.qst.binzhou2022.mapper.StuInfoMapper;
import com.qst.binzhou2022.mapper.UserInfoMapper;
import com.qst.binzhou2022.utils.MybatisUtils;
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
import java.io.IOException;
import java.io.InputStream;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;
public class StuInfoService {
/*
Mybatis接口代理
*/
public StuInfo getStuInfo(String stuCode) throws SQLException, IOException {
StuInfoMapper stuInfoMapper = (StuInfoMapper) MybatisUtils.getInstance().getMapper(StuInfoMapper.class);
StuInfo stuInfo = stuInfoMapper.getStuInfo(stuCode);
System.out.println(stuInfo.toString());
System.out.println(stuInfo.getClassInfo().toString());
return stuInfo;
}
}
准备工作5:随便编写一下jsp
和servlet
那么结果:
总结
- 映射文件的
resultMap
映射标签添加association
标签,作为另一个表的映射 , 处理一对一
<resultMap id="stuInfoMap" type="stuInfo">
<id column="stu_id" property="stuId"></id>
<result column="stu_name" property="stuName"></result>
<result column="stu_code" property="stuCode"></result>
<result column="class_id" property="classId"></result>
<association property="classInfo" javaType="classInfo">
<id column="class_id" property="classId"></id>
<result column="class_name" property="className"></result>
<result column="class_grade" property="classGrade"></result>
</association>
</resultMap>
注意association的属性
<association property="classInfo" javaType="classInfo">
property 类的属性名
javaType 用哪个类的对象给属性赋值
- 在实体类中增加对应的类关系;以属性体现
public class StuInfo {
private int stuId;
private String stuCode;
private String stuName;
private int classId;
private ClassInfo classInfo; // 这里哦
}
这样一看,更改的地方并不多,也不困难
11. 一对多查询
业务需求是查询某个班级的信息和这个班级所有学生的信息
对应sql
select *
from class_info
left join stu_info si on class_info.class_id = si.class_id
where si.class_id = #{classId}
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Zfhwauuk-1678960789413)(D:\其他\桌面\毕设\JavaWeb项目.assets\image-20230306164604722.png)]
准备工作一:增加ClassInfo属性:List<StuInfo>
准备工作二:创建ClassInfoMapper接口
准备工作三:创建映射文件
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"https://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.qst.binzhou2022.mapper.ClassInfoMapper">
<resultMap id="classInfoMap" type="classInfo">
<id column="class_id" property="classId"></id>
<result column="class_name" property="className"></result>
<result column="class_grade" property="classGrade"></result>
<collection property="stuInfoList" javaType="list" ofType="stuInfo">
<id column="stu_id" property="stuId"></id>
<result column="stu_name" property="stuName"></result>
<result column="stu_code" property="stuCode"></result>
<result column="class_id" property="classId"></result>
</collection>
</resultMap>
<select id="getClassJoinStu" resultMap="classInfoMap">
select *
from class_info
left join stu_info si on class_info.class_id = si.class_id
where si.class_id = #{classId}
</select>
</mapper>
准备工作四:编写ClassInfoService
package com.qst.binzhou2022.services;
import com.qst.binzhou2022.beans.ClassInfo;
import com.qst.binzhou2022.beans.StuInfo;
import com.qst.binzhou2022.mapper.ClassInfoMapper;
import com.qst.binzhou2022.mapper.StuInfoMapper;
import com.qst.binzhou2022.utils.MybatisUtils;
import java.io.IOException;
import java.sql.SQLException;
public class ClassInfoService {
/*
Mybatis接口代理
*/
public ClassInfo getClassInfo(int classId) throws SQLException, IOException {
ClassInfoMapper classInfoMapper = (ClassInfoMapper) MybatisUtils.getInstance().getMapper(ClassInfoMapper.class);
ClassInfo classInfo = classInfoMapper.getClassJoinStu(classId);
return classInfo;
}
}
准备工作五:编写相应的jsp和servlet
效果演示:
总结
- 在实体类中增加对应的类关系;以属性体现;注意是集合
public class ClassInfo {
private int classId;
private String className;
private String classGrade;
List<StuInfo> stuInfoList;
}
collection
标签 处理一对多
<resultMap id="classInfoMap" type="classInfo">
<id column="class_id" property="classId"></id>
<result column="class_name" property="className"></result>
<result column="class_grade" property="classGrade"></result>
<collection property="stuInfoList" javaType="list" ofType="stuInfo">
<id column="stu_id" property="stuId"></id>
<result column="stu_name" property="stuName"></result>
<result column="stu_code" property="stuCode"></result>
<result column="class_id" property="classId"></result>
</collection>
</resultMap>
property 类的属性名
javaType 可以省略;也可以写集合类型
ofType 用哪个类的对象给属性赋值(list中的类型)
12. 多对多
查询某个学生信息和该学生所选课信息
对应关系
实体类对应关系
编写映射文件SQL
<resultMap id="stuInfoMap2023" type="stuInfo">
<id column="stu_id" property="stuId"></id>
<result column="stu_name" property="stuName"></result>
<result column="stu_code" property="stuCode"></result>
<result column="class_id" property="classId"></result>
<collection property="stuCourseInfoList" javaType="list" ofType="stuCourseInfo">
<id column="stu_id" property="stuId"></id>
<id column="course_id" property="courseId"></id>
<association property="courseInfo" javaType="courseInfo">
<id column="course_id" property="courseId"></id>
<result column="course_name" property="courseName"></result>
<result column="course_text" property="courseText"></result>
</association>
</collection>
</resultMap>
<select id="getStuInfo2023" resultMap="stuInfoMap2023">
select *
from stu_info si
left join stu_course_info sci on si.stu_id = sci.stu_id
left join course_info ci on sci.course_id = ci.course_id
where si.stu_id = #{stuId}
</select>
因为Stu和StuCourse是一对多,所以用collection
因为StuCourse和Course是一对一,所以用association
其他的:增加实体类属性,编写接口、service方法,修改servlet、jsp。这些就不写了,和前面俩差不多。
13. 级联查询
级联查询,是利于数据库表间的外键关联关系进行自动的级联查询操作,除了实体类增加关联属性外,还需要在映射文件中进行配置。
以一对多为例;根据查询一个班级的所有学生信息;我们之前写的sql是通过多表连接查询;但是实际上我们可以将该SQL变更为2个sql
1、先查询班级信息
2、在根据班级信息查询学生信息
select *
from class_info
left join stu_info si on class_info.class_id = si.class_id
where si.class_id = #{classId}
将上述sql
转为两个sql
,上述sql
的结果是下面两个sql
结果之和,他们都用classId
查询
select * from class_info where class_id = #{classId}
select * from stu_info where class_id = #{classId}
发现classId是关键数据
- 首先编写
StuInfoMapper
增加根据class_id
查询学生信息的sql
<select id="findStuInfoByClassId" resultType="stuInfo" parameterType="int">
select stu_id stuId,stu_name stuName,stu_code stuCode ,class_id classId from stu_info s where s.class_id = #{classId}
</select>
映射在自己内部实现,可以这样,也可以用resultMap
- 编写
ClassInfoMapper
中 根据classid
查询班级信息的sql
;这里用到级联查询 所以并不能使用简单的返回类型
因为我们的目的是和一对多一样,要classInfo
和list<stuInfo>
,所以不是简单的resultMap
<resultMap id="ClassInfoAndStuInfo" type="classInfo">
<id column="class_id" property="classId"></id>
<result column="class_name" property="className"></result>
<result column="class_grade" property="classGrade"></result>
<collection property="stuInfoList" javaType="list" column="class_id"
select="com.qst.binzhou2022.mapper.StuInfoMapper.getStuInfoByClassId">
</collection>
</resultMap>
<select id="getClassInfoById" resultMap="ClassInfoAndStuInfo">
select *
from class_info
where class_id = #{classId}
</select>
发现,实际上是执行了两条SQL
懒汉加载和饿汉加载
默认是饿汉模式:前面我们使用的一个级联查询的方式是饿汉加载;无论如何都会进行2次查询
懒汉加载(延迟加载)
延迟加载的内容等到真正使用时才去进行加载(查询)。多用在关联对象或集合中。
延迟加载的好处:先从单表查询、需要时再从关联表去关联查询,大大降低数据库在单位时间内的查询工作量,将工作在时间上的分配更加均匀,而且单表要比关联查询多张表速度要快。
设置属性fetchType
为lazy
<resultMap id="ClassInfoAndStuInfo" type="classInfo">
<id column="class_id" property="classId"></id>
<result column="class_name" property="className"></result>
<result column="class_grade" property="classGrade"></result>
<collection property="stuInfoList" column="class_id" javaType="list" fetchType="lazy"
select="com.qst.binzhou2022.mapper.StuInfoMapper.getStuInfoByClassId">
</collection>
</resultMap>
验证:第一种虽然使用了classInfo
, 但没有对StuInfoList
做调用,所以仅需要一个sql
第二种:调用了StuInfoList
,那么在调用时去查询,即实现了延迟查询
javaType="list" 实体类的属性数据类型
column="class_id" 给另一个SQL语句传入的参数列
jdbcType="INTEGER" 参数对应JDBC的数据类型(实验,不加也没事)
fetchType="eager" 加载方式 eager饿汉加载 lazy延迟加载
aggressiveLazyLoading属性介绍 (翻译:有点亢奋的懒😎)
在懒加载模式下;只有当我们调用到级联查询后者数据的时候才会按需加载;
那如果我们希望只要该对象被使用就加载该对象所有数据呢??
使用aggressiveLazyLoading属性;该属性默认false
主配置文件中进行配置
<settings>
<setting name="aggressiveLazyLoading" value="true"/>
</settings>
效果:
以前是按需加载,现在是用即加载。
14. Mybatis注解开发
在接口中方法上添加注解
在简单sql时,可以直接这样,方便
@Select("select class_id classId, class_name className from class_info where class_id=#{classId}")
ClassInfo getById(int classId);
甚至,如果全用注解,可以不用映射文件,直接一个接口
需要在核心文件中添加接口的地址(类用.
,resource用/
),此方法了解即可
三、SpringMVC
1. 分析理解MVC
Mybatis替换的是 DAO连接数据库
SpringMVC替换的是 前端和servlet这部分
大致:
model:数据库的数据,也就是类
view:展示的界面
controller:控制数据展示到视图上
SpringMVC工作流程
HandlerMapping处理映射处理器
DispatcherServlet
不处理任何请求,但是接收全部请求
HandlerMapping
处理请求,处理器Handler
和URL
的映射关系
这样DispatcherServlet
就知道了往哪里转发请求,即给哪个Handler
HandlerAdapter处理器适配器
调用Handler
(或说servlet
),中间有个适配器是为了解耦,Handler
是我们编写的业务代码(Controller)
ViewReslover视图解析器
返回视图时做一个解析操作,在返回真正的视图字符串之前找到视图字符串在哪里,jsp?xml?
2. SpringMVC引入
依赖:
<!-- https://mvnrepository.com/artifact/org.springframework/spring-webmvc -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>5.3.18</version>
</dependency>
配置前端控制器,dispatcherServlet
就是一个Servlet
,之前说了,dispatcherServlet
需要接收全部请求
注意:/
拦截所有的非jsp
,/*
拦截所有请求
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Emb2FdBL-1678960789414)(D:\其他\桌面\毕设\JavaWeb项目.assets\image-20230312161534737.png)]
<!DOCTYPE web-app PUBLIC
"-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
"http://java.sun.com/dtd/web-app_2_3.dtd" >
<web-app
xmlns="http://xmlns.jcp.org/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee
http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd" version="3.1">
<servlet>
<servlet-name>dispatcherServlet</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
这里一会需要加东西哦
</servlet>
<servlet-mapping>
<servlet-name>dispatcherServlet</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
</web-app>
上面很清晰,就是一个正常的servlet配置,(一个servlet配置俩标签)
设置mvc配置文件,这样创建可以直接生成一些属性
配置文件中是对Springmvc各个组件配置的,现在我们什么都没有配置,但是需要加载这个xml配置文件,即springmvc
怎么知道这个文件是他的配置文件呢
DispatcherServlet
会默认加载/WEB-INF
下的指定名字的配置文件(默认名字为《Servlet-name》的值 拼接上 -servlet.xml)
例如在我们例子中会拼接/WEB-INF/dispatcherServlet-servlet.xml
— 不明白!!!!!!!!!!!
正常情况下DispatcherServlet
在初始化的时候我们可以通过给其contextConfigLocation
传递参数进行设置 配置文件所在路径
<!DOCTYPE web-app PUBLIC
"-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
"http://java.sun.com/dtd/web-app_2_3.dtd" >
<web-app
xmlns="http://xmlns.jcp.org/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee
http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd" version="3.1">
<servlet>
<servlet-name>dispatcherServlet</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<!-- 默认寻找同级目录下 也就是/WEB-INF/dispatcherServlet-servlet.xml -->
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:springmvc-config.xml</param-value> 这样就知道了mvc的配置文件在哪里
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>dispatcherServlet</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
</web-app>
3. SpringMVC初步配置
基础配置;配置三大组件和我们业务控制器
新建一个Controller包;并且在该包下面创建一个TestUserInfoController
实际上controller不会这样使用,这里只是做一个测试(如果这样一个handler写一个类,不和servlet一样了吗)
在springmvc-config.xml中添加业务控制器以及三大组件的基础配置
目前项目内部既有之前
servlet
的精准映射(如/UserInfoServlet
),也有刚刚配置的dispatcherServlet
的/
映射
如果调用/UserInfoServlet
,也是可以成功的;这因为一个Servlet配置优先级的问题精准的映射和/的优先级。一定是精准url优先级高
完善视图解析器
正常开发jsp我们一般会放到安全目录下;因此我们在WEB-INF下新建一个jsp目录存放我们的jsp页面
那么这样,jsp只能通过handler去访问把我们原先的jsp页面都移动过去
同时修改TestUserInfoController代码
现在我们设置jsp的路径还是挺长(
/WEB-INF/jsp/login.jsp
),感觉有点麻烦,这个时候我们就可以使用视图解析器的配置对返回值进行优化,修改配置文件中视图解析器;增加两个属性<!-- 配置视图解析器 和属性 --> <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver"> <property name="prefix" value="/WEB-INF/jsp/"></property> <property name="suffix" value=".jsp"></property> </bean> 这样在访问的路径上自动添加前缀和后缀
基于SpringMVC注解改造项目
上面基础的改造我们发现实际上并没有比Servlet简化多少
org.springframework.stereotype.Controller
注解类型用于将类的实例指定为控制器,使用@Controller
注解标注的类无需继承特定的类或实现特定的接口1、在
SpringMVC
配置文件中引入spring-context
;2、使用
<context:component-scan/>
元素配置包的扫描范围;3、在对应的类中使用
@Controller
对类进行注解。<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context" xmlns:mvc="http://www.springframework.org/schema/mvc" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/mvc https://www.springframework.org/schema/mvc/spring-mvc.xsd"> <!-- 配置handler处理器,用于相应/testController请求--> <!-- <bean name="/testController" class="com.qst.binzhou2022.controller.TestController"></bean>-- <!-- 上面的就不需要了,一个Controller(handler)配置一个url太麻烦,用下面统一扫描handler和url关系--> <!-- 配置包扫描范围,先找到所有的controller --> <context:component-scan base-package="com.qst.binzhou2022.controller"></context:component-scan> <!-- 增加注解依赖驱动 --> <mvc:annotation-driven></mvc:annotation-driven> <bean class="org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping"></bean> <bean class="org.springframework.web.servlet.mvc.SimpleControllerHandlerAdapter"></bean> <!-- 配置视图解析器--> <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver"> <property name="prefix" value="/WEB-INF/jsp/"></property> <property name="suffix" value=".jsp"></property> </bean> </beans>
新建一个Controller测试
出500错误原因:如没有请忽略
我们都知道,在idea中如果写List,idea会给我们推荐import含有List的包,我们一般选择的是util包
如果选择错,那么List就不是我们常用的那个List,就会出错。同理,我们在配置mvc文件时,也有可能出现上述问题
4. 静态资源问题
我们前端的常见资源有js css img等等 这些都会被dispatcherServlet拦截到
对资源放行
<!-- mapping代表访问的URL **代表任意层级,location是webapp下面的文件地址 --> <!-- <mvc:resources mapping="/js/**" location="/js/"></mvc:resources>--> <!-- <mvc:resources mapping="/img/**" location="/img/"></mvc:resources>--> <!-- <mvc:resources mapping="/css/**" location="/css/"></mvc:resources>--> <mvc:resources mapping="/static/**" location="/static/"></mvc:resources>
5. 常用注解
5.1 @RequestMapping
5.2 @PathVariable与RESTFUL风格
传统的url:*****/contextPath/getUserInfo?userId=10&userName=a
restful风格的url:*****/contextPath/getUserInfo/10/a
SpringMVC中实现restful风格的url访问
HTTP中RESTFULE支持
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-lhY8Xjio-1678960789415)(D:\其他\桌面\毕设\JavaWeb项目.assets\e531cee5722d4584825551a282635ae3.png)]
在SpringMVC中实现
创建一个conreoller测试类 写上四个方法 在controller中标明method属性 @Controller public class RestFulController { @RequestMapping(value = "/userInfo/{userId}", method = RequestMethod.GET) public String getUserInfo(@PathVariable("userId") String userId) { System.out.println("--------GET"); return "restful"; } @RequestMapping(value = "/userInfo/{userId}", method = RequestMethod.POST) public String addUserInfo(@PathVariable("userId") String userId) { System.out.println("--------POST"); return "restful"; } @RequestMapping(value = "/userInfo/{userId}", method = RequestMethod.DELETE) public String delUserInfo(@PathVariable("userId") String userId) { System.out.println("--------DELETE"); return "restful"; } @RequestMapping(value = "/userInfo/{userId}", method = RequestMethod.PUT) public String updateUserInfo(@PathVariable("userId") String userId) { System.out.println("--------PUT"); return "restful"; } @RequestMapping("restful") public String restful(){ return "restful"; } }
创建一个jsp put和delete因为form的method不支持 采用传递参数的方式;但是单纯传参无法转换请求类型 <%@ page contentType="text/html;charset=UTF-8" language="java" isErrorPage="true" %> <html> <head> </head> <body> <form method="post" action="${pageContext.request.contextPath}/userInfo/1"> <input type="hidden" name="_method" value="put"> <input type="submit" value="更新put"> </form> <form method="post" action="${pageContext.request.contextPath}/userInfo/1"> <input type="hidden" name="_method" value="delete"> <input type="submit" value="删除delete"> </form> <form method="post" action="${pageContext.request.contextPath}/userInfo/1"> <input type="submit" value="新增post"> </form> <form method="get" action="${pageContext.request.contextPath}/userInfo/1"> <input type="submit" value="查询get"> </form> </body> </html> 注意isErrorPage="true",不加会出现405错误
现在还是不可以的,原因有二,
一是 html本身就没有put和delete方法
二是 虽然我们用hidden设置了,但请求中的method属性仍不会改变,在controller哪里仍不知道
解决:添加一个拦截器,拦截请求根据参数进行转化
<filter> <filter-name>hidden</filter-name> <filter-class>org.springframework.web.filter.HiddenHttpMethodFilter</filter-class> </filter> <filter-mapping> <filter-name>hidden</filter-name> <url-pattern>/*</url-pattern> </filter-mapping>
5.3 @RequestHeader
用于快捷获取请求头中信息
@RequestMapping("/toLogin")
public String toLogin(@RequestHeader("Accept") String accept, @RequestHeader("Accept-Language") String lan) {
System.out.println(accept);
System.out.println(lan);
return "login";
}
5.4 @CookieValue
快捷获取cookie信息
6. 参数注入
6.1 乱码问题
get方式如果乱码可以修改server.xml
这里使用tomcat版本较高并没有问题
将表单修改为post, 提交中文后台获取是乱码
web.xml中用过滤器转码
此处要注意一定要写到所有过滤器之前
<!--springMVC提供的编码过滤器-->
<filter>
<filter-name>encoding</filter-name>
<filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
<init-param>
<param-name>encoding</param-name>
<param-value>UTF-8</param-value>
</init-param>
<init-param>
<param-name>forceEncoding</param-name>
<param-value>true</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>encoding</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
6.2 时间Date
@DateTimeFormat(pattern = "yyyy-MM-dd")
单独时间
@RequestMapping("time") public String time(@DateTimeFormat(pattern = "yyyy-MM-dd") Date userTime) { System.out.println(userTime); return "restful"; }
实体类中有时间属性
在bean中添加注解
bean: @DateTimeFormat(pattern = "yyyy-MM-dd") private Date userTime; controller中正常: @RequestMapping("timeClass") public String timeClass(UserInfo userInfo) { System.out.println(userInfo.toString()); return "restful"; }
7. 响应返回
7.1 转发和重定向
转发到其他Controller
return "forward:/restful";
重定向
return "redirect:/restful";
7.2 响应Json
@ResponseBody
:可以直接将返回的字符串数据作为响应内容,即设置controller
返回的是一个Json
数据,而不是视图
添加依赖:这个依赖我们直接用不到,是SpringMVC去调用的,如果不加会406错误
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.13.1</version>
</dependency>
@Controller
public class JsonController {
@ResponseBody
@RequestMapping("getUserInfoJson")
public UserInfo getUserInfoJson() {
return new UserInfo(1,"zhangsan1",12,"1","",new Date());
}
@ResponseBody
@RequestMapping("/getUserInfoJsonList")
public List<UserInfo> json(){
List<UserInfo> list = new ArrayList<UserInfo>();
list.add(new UserInfo(1,"zhangsan1",12,"1","",new Date()));
list.add(new UserInfo(2,"zhangsan2",12,"1","",new Date()));
list.add(new UserInfo(3,"zhangsan3",12,"1","",new Date()));
return list;
}
}
在需要范围Json数据的方法上设置ResponseBody
如果整个Controller类的方法都是返回Json
@RestController
的作用等同于@Controller + @ResponseBody
@RestController
//@Controller
public class JsonController {
// @ResponseBody
@RequestMapping("getUserInfoJson")
public UserInfo getUserInfoJson() {
return new UserInfo(1,"zhangsan1",12,"1","",new Date());
}
// @ResponseBody
@RequestMapping("/getUserInfoJsonList")
public List<UserInfo> json(){
List<UserInfo> list = new ArrayList<UserInfo>();
list.add(new UserInfo(1,"zhangsan1",12,"1","",new Date()));
list.add(new UserInfo(2,"zhangsan2",12,"1","",new Date()));
list.add(new UserInfo(3,"zhangsan3",12,"1","",new Date()));
return list;
}
}
8. 数据校验
一般情况下我们都会使用前端校验+后端校验的方式,在springmvc中如何进行后端数据校验。
JSR303是 Java 为 Bean 数据合法性校验提供的标准框架,它已经包含在 JavaEE 6.0 中 。通过在 Bean 属性上标注类似于 @NotNull、@max 等标准的注解指定校验规则,并通过标准的验证接口对 Bean 进行验证。
spring中拥有自己的数据校验框架,同时支持JSR303标准的校验框架,可以在通过添加注解的方式进行数据校验。在spring中本身没有提供JSR303的实现,需要导入依赖的包。
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-validator</artifactId>
<version>5.1.0.Final</version>
</dependency>
在实体类中添加约束
在方法属性设置此类需要被检查
@Valid
,且设置了接受错误信息的BindingResult
@RequestMapping("register")
public String register(@Valid UserInfo userInfo, BindingResult bindingResult) {
if (bindingResult.hasErrors()) {
List<FieldError> errors = bindingResult.getFieldErrors();
for (FieldError error : errors) {
System.out.println(error.getDefaultMessage());
}
}
System.out.println("-----------");
System.out.println(userInfo.toString());
return "login";
}
用
message
可设置错误返回的消息
9. 文件上传
SpringMVC为文件上传提供了直接支持,即MultipartResolver接口,具有即插即用特征。SpringMVC提供了CommonsMultipartResolver类,该类通过Commons FileUpload技术实现了MultipartResolver接口。因此使用SpringMVC上传功能时需要提供Commons FileUpload依赖支持。
JavaWeb:前端–数据流–转成文件
MVC:前端–注入成
MultipartResolver
类
<!-- 文件上传 -->
<!-- https://mvnrepository.com/artifact/commons-io/commons-io -->
<dependency>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
<version>2.6</version>
</dependency>
<!-- https://mvnrepository.com/artifact/commons-fileupload/commons-fileupload -->
<dependency>
<groupId>commons-fileupload</groupId>
<artifactId>commons-fileupload</artifactId>
<version>1.4</version>
</dependency>
在SpringMVC配置文件中增加配置
<bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver"> <property name="defaultEncoding" value="UTF-8"></property> <property name="maxUploadSize" value="1024000"></property> </bean>
@RequestMapping("register")
public String register(MultipartFile userImg) throws IOException {
System.out.println(userImg.getOriginalFilename());
String fileUrl = "D:\\" + userImg.getOriginalFilename();
File file = new File(fileUrl);
//正常我们文件会选择放到本项目路径下或者选择一个文件服务器作为存储
userImg.transferTo(file);
return "login";
}
10. 文件下载
没什么可说的,都是套路
@RequestMapping("/downloadFile")
public ResponseEntity<byte[]> download(HttpServletRequest request) throws Exception {
//获取要下载文件的路径及输入流对象
ServletContext servletContext = request.getServletContext();
String realPath = servletContext.getRealPath("/static/img/logo.jpg");
FileInputStream fileInputStream = new FileInputStream(realPath);
byte[] bytes = new byte[fileInputStream.available()];
fileInputStream.read(bytes);
fileInputStream.close();
//将要下载文件内容返回
HttpHeaders httpHeaders = new HttpHeaders();
httpHeaders.set("Content-Disposition","attachment;filename=logo.jpg");
return new ResponseEntity<byte[]>(bytes,httpHeaders, HttpStatus.OK);
}
11. 拦截器
SpringMVC中提供了Interceptor拦截器,用于拦截用户的请求并进行相应的处理,如权限认证、判断用户是否登录等。
SpringMVC拦截器是一种可插拔式的设计,当需要使用某个拦截器时,只需要在配置文件中应用该拦截器即可;当不需要该拦截器时,取消配置文件中的配置。
SpringMVC中的拦截器是通过实现
HandlerInterceptor
接口或继承HandlerInterceptorAdapter
抽象类来完成的。
重写三个方法
preHandle
这个方法在业务处理器处理请求之前被调用,在该方法中对用户请求 request 进行处理。如果程序员决定该拦截器对请求进行拦截处理后还要调用其他的拦截器,或者是业务处理器去进行处理,则返回true;如果程序员决定不需要再调用其他的组件去处理请求,则返回false
当preHandle()方法返回true时,继续向下执行处理器中的方法,否则不再向下执行
postHandle
当preHandle()方法返回true时,且在
Controller
方法被调用之后被调用; postHandle()方法在DispatcherServlet
进行视图返回渲染之前被调用,因此在方法中可以对Controller处理之后的ModelAndView对象进行操作;
afterCompletion
这个方法在DispatcherServlet完全处理完请求后被调用,可以在该方法中进行一些资源清理的操作。
public class TestInterceptor1 implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
System.out.println("一:1111111111");
return HandlerInterceptor.super.preHandle(request, response, handler); // true
}
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
System.out.println("一:2222222222");
HandlerInterceptor.super.postHandle(request, response, handler, modelAndView);
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
System.out.println("一:3333333333");
HandlerInterceptor.super.afterCompletion(request, response, handler, ex);
}
}
springMVC配置:
<mvc:interceptors>
<mvc:interceptor>
<mvc:mapping path="/*"/>
<bean class="com.qst.binzhou2022.utils.TestInterceptor1"></bean>
</mvc:interceptor>
</mvc:interceptors>
如果配置多个拦截器;拦截器的执行顺序是根据配置顺序来的
四、Spring
spring是一个开源框架。
spring是为了简化企业开发而生的,使得开发变得更加优雅和简洁。
spring是一个IOC和AOP的容器框架。
容器:包含并管理应用对象的生命周期,就好比用桶装水一样,spring就是桶,而对象就是水 servletcontext
使用Spring的优点
1、Spring通过DI、AOP和消除样板式代码来简化企业级Java开发
2、Spring框架之外还存在一个构建在核心框架之上的庞大生态圈,它将Spring扩展到不同的领域,如Web服务、REST、移动开发以及NoSQL
3、低侵入式设计,代码的污染极低
4、独立于各种应用服务器,基于Spring框架的应用,可以真正实现Write Once,Run Anywhere的承诺
5、Spring的IoC容器降低了业务对象替换的复杂性,提高了组件之间的解耦
6、Spring的AOP支持允许将一些通用任务如安全、事务、日志等进行集中式处理,从而提供了更好的复用
7、Spring的ORM和DAO提供了与第三方持久层框架的的良好整合,并简化了底层的数据库访问
8、Spring的高度开放性,并不强制应用完全依赖于Spring,开发者可自由选用Spring框架的部分或全部
1. IOC
将对象交给IOC容器管理,使用时从IOC容器中获取。
容器也是一个对象,只不过内部管理着其他的对象
IOC是一种设计原则;降低代码耦合;
DI是最常见的实现方式IOC站在业务对象的角度上:业务对象将获取业务对象依赖的资源的控制权反转给容器。
DI站在容器的角度上:容器将业务对象依赖的资源注入到业务对象中
如何实现IOC?
Spring框架如何帮助我们实现用容器管理Bean(业务对象)
提供管理业务类的容器接口,BeanFactory
和ApplicationContext
如何告诉容器管理哪些类?
1.1 XML方式
① 属性注入
- 普通的不带属性的类
创建新配置文件
创建测试类(注意,上面的文件改了名字,将applicationContext改为了springConfig,其实无所谓,我觉得后者清晰一些)
public class TestSpring { @Test public void testSpringIOCXML(){ ClassPathXmlApplicationContext classPathXmlApplicationContext = new ClassPathXmlApplicationContext("springConfig.xml"); // 配置文件名 UserInfoController userInfoController = classPathXmlApplicationContext.getBean("userInfoController",UserInfoController.class); // UserInfoController userInfoController = (UserInfoController) classPathXmlApplicationContext.getBean("userInfoController"); System.out.println(userInfoController); } }
- 带有属性的类,且属性中有其他类
加入一些属性
配置文件
<!-- 因为userInfoController使用userInfo属性,所以在之前需要加入UserInfo --> <bean id="userInfo111" class="com.qst.binzhou2022.beans.UserInfo"> <property name="userName" value="111"></property> </bean> <!-- 容器 存放对象 --> <bean id="userInfoController" class="com.qst.binzhou2022.controller.UserInfoController"> <property name="name" value="我是Name"></property> <property name="userInfo" ref="userInfo111"></property> <property name="userList"> <list> <ref bean="userInfo111"></ref> <bean id="userInfo222" class="com.qst.binzhou2022.beans.UserInfo"> <property name="userName" value="222"></property> </bean> </list> </property> </bean>
层级很明显
bean
中的基本属性用value
设置值,引用类型用ref
(也可以不引用,再套一个bean
),数组用list
,list
里面再根据类型继续嵌套写。list
里面的userInfo
用了两种,一种是rel
标签,一种直接写了一个bean
结果:
UserInfoController(name=我是Name, userInfo=UserInfo(userId=null, userName=111, userAge=null, userSex=null, userPass=null, userTime=null), userList=[UserInfo(userId=null, userName=111, userAge=null, userSex=null, userPass=null, userTime=null), UserInfo(userId=null, userName=222, userAge=null, userSex=null, userPass=null, userTime=null)])
- 标签的继承、依赖
如果有相同值的属性,可以继承
parent
依赖:像userInfoController
就依赖了userInfo111、userInfo222、userInfo333
即:在执行本体之前需要加载这三个,用depends-on
(不加也正常)<bean id="userInfo111" class="com.qst.binzhou2022.beans.UserInfo"> <property name="userName" value="111"></property> <property name="userAge" value="1888888888"></property> </bean> <bean id="userInfo222" class="com.qst.binzhou2022.beans.UserInfo" parent="userInfo111"> <property name="userName" value="222"></property> </bean> <bean id="userInfo333" class="com.qst.binzhou2022.beans.UserInfo" parent="userInfo111"> <property name="userName" value="222"></property> </bean> <!-- 容器 存放对象 --> <bean id="userInfoController" class="com.qst.binzhou2022.controller.UserInfoController" depends-on="userInfo111,userInfo222,userInfo333"> <property name="name" value="我是Name"></property> <property name="userInfo" ref="userInfo111"></property> <property name="userList"> <list> <ref bean="userInfo111"></ref> <ref bean="userInfo222"></ref> <ref bean="userInfo333"></ref> </list> </property> </bean>
UserInfoController(name=我是Name, userInfo=UserInfo(userId=null, userName=111, userAge=1888888888, userSex=null, userPass=null, userTime=null), userList=[UserInfo(userId=null, userName=111, userAge=1888888888, userSex=null, userPass=null, userTime=null), UserInfo(userId=null, userName=222, userAge=1888888888, userSex=null, userPass=null, userTime=null), UserInfo(userId=null, userName=222, userAge=1888888888, userSex=null, userPass=null, userTime=null)])
② 构造器注入
类中需要有全参构造函数
<bean id="userInfoController" class="com.qst.binzhou2022.controller.UserInfoController" depends-on="userInfo111,userInfo222,userInfo333"> <constructor-arg name="name" value="111"></constructor-arg> <constructor-arg name="userInfo" ref="userInfo111"></constructor-arg> <constructor-arg name="userList"> <list> <ref bean="userInfo111"></ref> <ref bean="userInfo222"></ref> <ref bean="userInfo333"></ref> </list> </constructor-arg> </bean>
③ 自动注入
A需要调用B,B是A的依赖资源,这时候需要自动注入。autowire
常用byType
:根据对应属性进行注入
<bean id="userInfo111" class="com.qst.binzhou2022.beans.UserInfo">
<property name="userName" value="111"></property>
<property name="userAge" value="1888888888"></property>
</bean>
<bean id="userInfoController" class="com.qst.binzhou2022.controller.UserInfoController" autowire="byType"
depends-on="userInfo111">
<property name="name" value="111"></property>
</bean>
UserInfoController(name=111,
userInfo=UserInfo(userId=null, userName=111, userAge=1888888888, userSex=null, userPass=null, userTime=null),
userList=[UserInfo(userId=null, userName=111, userAge=1888888888, userSex=null, userPass=null, userTime=null)])
1.2 注解方式
主要使用注解的方式,注解可以代替xm几乎的所有操作(包扫描需要在xml中)。
组件扫描(component scanning): Spring 能够从 classpath 下自动扫描, 侦测和实例化具有特定注解的组件.
@Component
: 标识了一个受 Spring 管理的基本组件(不推荐使用),代表自动创建了一个bean标签
@Repository
: 标识持久层组件类
@Service
: 标识服务层(业务层)组件类
@Controller
: 标识控制层组件类后三者和
Component
作用相同,不过有清晰的分层含义对于扫描到的组件, Spring 有默认的命名策略: 使用非限定类名, 第一个字母小写. 也可以在注解中通过
value
属性值标识组件的名称
首先,在xml中配置包扫描,同SpringMVC的包扫描
不过配置的范围需要扩大,因为不止是Controller
需要IOC
控制反转,(当然也可以多写几条context标签)
<context:component-scan base-package="com.qst.binzhou2022"></context:component-scan>
任务:在UserInfoController
中加入UserInfoService
属性,并对进行自动注入
UserInfoService
是引用类型,那么需要将他也加入到容器管理:在UserInfoService
上添加@Service
注解,代表之前的一个bean标签。
@Autowired
:标记userInfoService
属性需要自动注入- 自动注入的前提是在IOC容器中已经存在了
userInfoService
,上步加入的@Service
注解帮我们实现了 - 是否还记得xml中的
Autowire
自动注入呢?当时我们说常用byType
(根据属性进行注入),这里的**@Autowired
**就是根据属性进行注入(根据name用另一种注解,不常用) @Value
设置基本类型的默认值,不常用
- 自动注入的前提是在IOC容器中已经存在了
这样继续执行之前的测试类:
结果:获取到了userInfoService
UserInfoController(name=设置注入的默认值, userInfoService=com.qst.binzhou2022.services.UserInfoService@7068e664)
如果不对UserInfoService
做@Service
(加入IOC容器)以及@Autowired
(标记自动注入),那么他的结果是null
注意:如果不在spring配置文件中配置包扫描,会直接报错,因为根据找不到对应的类
其实,这四个注解无论用哪一个,其实都可以得到
UserInfoService
,都是根据UserInfoService
类路径生成一个对应的bean
标签。。。。。。。。。。。。。了解即可
了解:@Qualifier
:根据name自动注入
@Autowired
默认是根据类型进行配置的,并且也无法修改为ByName,这个时候如果容器中该类型的对象有多个则会出错(一般不会有多个)例我们在UserInfoService中增加一个属性,同时添加该Service 为userInfoService2
配置文件中我们在配置一个UserInfoService的对象 id设置为userInfoService1
那么目前IOC容器中存在有两个UserInfoService,这样启动测试类,出现错误使用
@Qualifier
指定注入的bean是哪一个
在Controller中增加@Qualifier("userInfoService1")
了解:@Resource
@Resource并不是Spring的注解,他的包是javax.annotation.Resource 需要导入。但是Spring支持该注解的注入。
@Resource有两个中重要的属性:name和type ,而Spring将@Resource注解的name属性解析为bean的名字,而type属性则解析为bean的类型。所以如果使用name属性,则使用byName的自动注入策略,而使用type属性时则使用 byType自动注入策略。如果既不指定name也不指定type属性,这时将通过反射机制使用byName自动注入策略
可以直接把@Autowired替换为@Resource;演示结果也是一样的;这里不做记录
1.3 配置类方式
了解即可 完全去除xml配置
Spring3.0支持将配置文件使用java类进行替代
我们新建一个config目录,在目录下新建一个SpringConfig类
给这个类添加@Configuration告诉容器这是一个配置类
然后通过ComponentScan告诉容器要扫描的路径
@Configuration @ComponentScan(basePackages = {"com.qst.binzhou2022"}) public class SpringConfig { }
在配置文件中声明bean对象 @Bean注解
@Configuration @ComponentScan(basePackages = {"com.qst.binzhou2022"}) public class SpringConfig { @Bean public StuInfo getStuInfo() { StuInfo stuInfo = new StuInfo(); stuInfo.setStuName("苏小白"); return stuInfo; } }
测试类
@Test public void test() { AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext("com.qst.binzhou2022"); StuInfo stuInfo = context.getBean(StuInfo.class); System.out.println(stuInfo); }
2. AOP
概念:链接点
从宏观角度来看,java程序执行的最基本单位是方法的调用。程序的运行过程是方法调用的过程。
链接点是发生方法调用部分的代码。比如A方法中有调用B方法的代码,那段代码就是链接点。
概念:AOP的作用
假如有一个写好的程序(程序中有方法A、B、C、D、E),现在临时需要在一些方法(B、D)之前添加一个相同的操作,比如统计某些方法的运行时间。
如果这样,我们可以在B、D方法内部修改其代码,但如果之后还有类似的操作,比如这次是A、D方法,我们仍需要在方法体内部添加、修改代码
这样的缺点是我们不断修改已经写好的代码
AOP可以解决这个问题,如果需要在B、D方法运行前、运行后做一些操作,我们可以创建一个代理类,用这个类来调用B、D方法,在执行这些方法前做相应的操作。好处是不用修改已经写好的B、D方法了
概念:切入点
继续用上次的例子,假如有一个写好的程序(程序中有方法A、B、C、D、E),现在临时需要在一些方法(B、D)之前添加一个相同的操作,比如统计某些方法的运行时间。
B、D
就是切入点,B、D
加起来就是一个切面(需要添加的功能相同)121
如果是A、C
方法,同理,A、C
就是切入点,A、C
加起来就是一个切面(需要添加的功能相同)这两个切面的功能不同,这就是切面(添加相同功能点的集合)、切入点(需要添加功能的点)
概念:代理类
代理类有三种实现方法,主要是后面两种。但我们其实不需要关注那么多,实际使用有Spring提供的注解
Spring会根据我们的对象,自动在两者间转换(有些场景适用于JDK…有些适用于CGLib)代理类继承目标类,就可以获取到目标方法的所有信息,包括参数
具体使用
Aspect扩展了标准ava语言,提供了整套AOP理论的完整实现
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aspects</artifactId>
<version>5.2.3.RELEASE</version>
</dependency>
execution
:匹配执行方法的连接点,是 Spring AOP中最主要的切入点指示符(也有其他的,但用这个就够了)execution(方法的修饰符 返回值类型 所属类.方法名(参数类型))
@Component
@Aspect
public class TimeAdvice {
//调用实际业务类方法之前 前置通知 链接点执行之前执行
@Before("execution(public * com.qst.binzhou2022.services.UserInfoService.getUserInfo(*))")
public void printBeforeTime(JoinPoint joinPoint) {
String methodName = joinPoint.getSignature().getName();
Object[] params = joinPoint.getArgs();
System.out.println("调用的方法名" + methodName +"参数" + params.length + "--" + "第一个参数:" + params[0].toString() );
System.out.println("前置通知 printBeforeTime <<<<<<<<<<<<" + new Date());
}
//调用实际业务类方法之后 后置通知是在连接点完成之后执行的, 无论实际业务方法是否出现异常,执行完都会执行后置通知,
//在此通知中,无法获取实际业务方法的返回值
@After("execution(public * com.qst.binzhou2022.services.UserInfoService.getUserInfo(*))")
public void printAfterTime() {
System.out.println("后置通知 printAfterTime >>>>>>>>>>>>"+new Date());
}
//返回通知 在方法正常结束后执行,可以获取实际业务方法的返回值
//@AfterReturning("execution(public int springAspectjAOP.aopclass.MathInterImpl.chu(int, int))")
@AfterReturning(value = "execution(public * com.qst.binzhou2022.services.UserInfoService.getUserInfo(*))",returning = "returnValue")
public void printReturnTime(JoinPoint joinPoint,Object returnValue) {
System.out.println("返回结果:" + returnValue);
System.out.println("返回通知 printReturnTime -----"+new Date());
}
//调用实际业务类方法出现异常的时候执行 异常通知 只在连接点抛出异常时才执行异常通知
@AfterThrowing(value="execution(public * com.qst.binzhou2022.services.UserInfoService.getUserInfo(*))",throwing="e")
public void printThrowTime(JoinPoint joinPoint, FileNotFoundException e) {
System.out.println("出现的异常" + e);
System.out.println("异常通知 printThrowTime xxxxxxxxxxxxxxxxxxxxxx" + new Date());
}
//环绕通知 相当于动态代理的实现 需要在环绕通知中指定目标方法执行,
//如何让目标方法执行,我们需要一个参数ProceedingJoinPoint类型的参数,通过该参数可以帮助我们获取执行的实际业务类方法名和参数,同时可以控制目标方法的执行与否
@Around("execution(public * com.qst.binzhou2022.services.UserInfoService.getUserInfo(*))")
public Object printAroundTime(ProceedingJoinPoint pj) {
System.out.println("环绕通知 printAroundTime =============="+new Date());
Object returnData = null;
//通过ProceedingJoinPoint参数获取方法名和参数
String methodName = pj.getSignature().getName();
Object [] args = pj.getArgs();
//通过ProceedingJoinPoint参数的proceed方法执行目标方法
try {
//前置通知
System.out.println("环绕--前置通知");
returnData = pj.proceed();
//返回通知
System.out.println("环绕--返回通知");
} catch (Throwable e) {
//异常通知
System.out.println("环绕异常通知");
e.printStackTrace();
}
//后置通知
System.out.println("环绕--后置通知");
return returnData;
}
}
五、SSM整合
1. M
三个框架整合到一起
目前来说,Spring主要帮我们解决的是依赖关系:
Controller-->Service-->Dao
(现在Dao层不用,而mapper接口也不用我们new,他是代理类帮我们创建的,除非将控制权再交给IOC )所以目前IOC帮我们的是:
Controller-->Service
回想:现在我们在Service中用的是手写的Mybatis工具类,工具类的流程是:
SqlSessionFactoryBuilder-->SqlSessionFactory-->SqlSession-->getMapper
原先我们使用
Mybatis
就是在service
层中使用sqlsessionfactory
和sqlsession
获取mapper代理类
既然我们要解决依赖关系;需要解决Service
调用代理类的依赖
所以我们可以思考出来
我们需要把sqlsessionfactory
和sqlsession
获取mapper
代理类的创建都交给Spring
mapper
代理类可以由SqlSession
创建
所以最终我们只需要让Spring
管理我们sqlsessionfactory
和sqlsession
对象即可我们的最终目的是IOC可以帮我们管理
Controller、Service、mapper
引入依赖
- mybatis写的帮助我们在IOC中管理
sqlsessionfactory
和sqlsession
- 数据库连接词,用别的也可以
- 既然在IOC中管理Mybatis的东西,那么关系数据库的连接信息也要在IOC中设置。这个是spring连接数据库相关
<dependency> <groupId>org.mybatis</groupId> <artifactId>mybatis-spring</artifactId> <version>2.0.4</version> </dependency> <dependency> <groupId>com.alibaba</groupId> <artifactId>druid</artifactId> <version>1.1.10</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-jdbc</artifactId> <version>5.2.3.RELEASE</version> </dependency>
2. Spring加入M
创建一个applicationContext.xml
(名字不太重要)配置文件,进行Spring配置文件配置。之前的SpringConfig没有用处了。
- 数据库信息
我们需要让IOC创建sqlsessionfactory和sqlSession;必然要链接数据库,所以数据库信息需要配置到IOC中
数据库信息配置在jdbc.properties中,使用
<context:property-placeholder>
引入属性文件将数据库链接信息放到一个对象中,方便注入,这里使用的是DruidDataSource
<context:property-placeholder location="classpath:jdbc.properties"></context:property-placeholder> <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource"> <property name="driverClassName" value="${jdbc_driver}"/> <property name="url" value="${jdbc_url}"/> <property name="username" value="${jdbc_username}"/> <property name="password" value="${jdbc_password}"/> </bean>
- 创建
sqlsessionfactory
这里不需要直接创建Mybaits核心包中的类(SqlSessionFactory)
我们引入了mybatis整合Spring的包,使用其中的SqlSessionFactoryBean
更加适合(mybatis专门为了spring准备的)
同时配置需要注入必须的数据源,以及常用的别名扫描配置<bean id="sessionFactoryBean" class="org.mybatis.spring.SqlSessionFactoryBean"> <property name="dataSource" ref="dataSource"></property> <property name="typeAliasesPackage" value="com.qst.binzhou2022.beans"></property> </bean>
- 创建
sqlSession
同样的,这里我们也不需要单独配置管理
sqlsession
,而选择使用整合包给我们提供的一个映射文件扫描器类MapperScannerConfigurer
一个映射文件扫描器
主要的作用就是扫描映射文件,生成mapper代理类。因此我们需要至少配置两个属性
- 要扫描哪些映射文件
- 扫描出来的映射文件使用哪个
sqlSessionFactory
去处理生成代理类<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer"> <property name="basePackage" value="com.qst.binzhou2022.mapper"></property> <property name="sqlSessionFactoryBeanName" value="sessionFactoryBean"></property> </bean>
- 添加扫描
service
层的配置
<context:component-scan base-package="com.qst.binzhou2022.services"></context:component-scan>
Controller层已经在
springmvc-config.xml
中扫描过了,不需要再扫描,如果再写一个,SpringIOC会生成两个Controller对象。(SpringMVC在创建时就有一个SpringIOC容器)
3. Spring容器配置
容器配置好了以后,我们要思考一下容器存放位置,在servletContext容器启动后就加载SpringIOC容器
经过思考放到应用域是最合适的
所以当应用域启动的时候我们就应该创建我们的ioc容器加载对象资源在
web.xml
中增加项目启动创建spring
容器的配置
这里要使用到监听器监听应用域启动创建IOC
容器
ContextLoaderListener
是Spring
框架帮我们提供好的监听器直接配置使用即可<context-param> <param-name>contextConfigLocation</param-name> <param-value>classpath:applicationContext.xml</param-value> Spring配置文件地址 </context-param> <listener> <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class> </listener>
4. 全部配置文件
4.1 Spring配置
applicationContext.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/aop https://www.springframework.org/schema/aop/spring-aop.xsd">
<context:property-placeholder location="classpath:jdbc.properties"></context:property-placeholder>
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
<property name="driverClassName" value="${jdbc_driver}"/>
<property name="url" value="${jdbc_url}"/>
<property name="username" value="${jdbc_username}"/>
<property name="password" value="${jdbc_password}"/>
</bean>
<bean id="sessionFactoryBean" class="org.mybatis.spring.SqlSessionFactoryBean">
<property name="dataSource" ref="dataSource"></property>
<property name="typeAliasesPackage" value="com.qst.binzhou2022.beans"></property>
</bean>
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<property name="basePackage" value="com.qst.binzhou2022.mapper"></property>
<property name="sqlSessionFactoryBeanName" value="sessionFactoryBean"></property>
</bean>
<context:component-scan base-package="com.qst.binzhou2022.services"></context:component-scan>
<!-- 扫描AOP的包 -->
<context:component-scan base-package="com.qst.binzhou2022.utils"></context:component-scan>
<aop:aspectj-autoproxy></aop:aspectj-autoproxy>
</beans>
jdbc.properties
jdbc_driver=com.mysql.cj.jdbc.Driver
jdbc_url=jdbc:mysql://127.0.0.1:3306/guangxissmdb?useSSL=false&useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai
jdbc_username=root
jdbc_password=root
4.2 SpringMVC配置
springmvc-config.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:mvc="http://www.springframework.org/schema/mvc"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/mvc https://www.springframework.org/schema/mvc/spring-mvc.xsd">
<!-- 配置包扫描范围,先找到所有的controller -->
<context:component-scan base-package="com.qst.binzhou2022.controller"></context:component-scan>
<!-- 增加注解依赖驱动 -->
<mvc:annotation-driven></mvc:annotation-driven>
<bean class="org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping"></bean>
<bean class="org.springframework.web.servlet.mvc.SimpleControllerHandlerAdapter"></bean>
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<property name="prefix" value="/WEB-INF/jsp/"></property>
<property name="suffix" value=".jsp"></property>
</bean>
<!-- mapping代表访问的URL **代表任意层级,location是webapp下面的文件地址 -->
<mvc:resources mapping="/static/**" location="/static/"></mvc:resources>
<bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
<property name="defaultEncoding" value="UTF-8"></property>
<property name="maxUploadSize" value="1024000"></property>
</bean>
<!-- 拦截器地址 -->
<mvc:interceptors>
<mvc:interceptor>
<mvc:mapping path="/toLogin"/>
<bean class="com.qst.binzhou2022.utils.TestInterceptor1"></bean>
</mvc:interceptor>
</mvc:interceptors>
</beans>
4.3 web.xml
<!DOCTYPE web-app PUBLIC
"-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
"http://java.sun.com/dtd/web-app_2_3.dtd" >
<web-app
xmlns="http://xmlns.jcp.org/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee
http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd" version="3.1">
<!-- Spring容器:监听器监听应用域启动创建IOC容器 -->
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:applicationContext.xml</param-value>
</context-param>
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<!--springMVC提供的编码过滤器-->
<filter>
<filter-name>encoding</filter-name>
<filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
<init-param>
<param-name>encoding</param-name>
<param-value>UTF-8</param-value>
</init-param>
<init-param>
<param-name>forceEncoding</param-name>
<param-value>true</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>encoding</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
<!-- dispatcherServlet -->
<servlet>
<servlet-name>dispatcherServlet</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<!-- 默认寻找同级目录下 也就是/WEB-INF/dispatcherServlet-servlet.xml -->
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:springmvc-config.xml</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>dispatcherServlet</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
<!-- HTTP中RESTFULE支持(put、delete请求):添加一个拦截器,拦截请求根据参数进行转化 -->
<filter>
<filter-name>hidden</filter-name>
<filter-class>org.springframework.web.filter.HiddenHttpMethodFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>hidden</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
</web-app>
4.4 依赖
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>3.8.1</version>
<scope>test</scope>
</dependency>
<!-- servlet-api -->
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>4.0.1</version>
</dependency>
<!-- 数据库jar -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.27</version>
</dependency>
<!-- jsp标签库 -->
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>jstl</artifactId>
<version>1.2</version>
</dependency>
<!-- lombok -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.26</version>
<scope>provided</scope>
</dependency>
<!-- mybatis -->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.5.11</version>
</dependency>
<!-- log4j2 -->
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-core</artifactId>
<version>2.20.0</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.springframework/spring-webmvc -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>5.3.18</version>
</dependency>
<!-- Json -->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.13.1</version>
</dependency>
<!-- 数据校验 -->
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-validator</artifactId>
<version>5.1.0.Final</version>
</dependency>
<!-- 文件上传 -->
<!-- https://mvnrepository.com/artifact/commons-io/commons-io -->
<dependency>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
<version>2.6</version>
</dependency>
<!-- https://mvnrepository.com/artifact/commons-fileupload/commons-fileupload -->
<dependency>
<groupId>commons-fileupload</groupId>
<artifactId>commons-fileupload</artifactId>
<version>1.4</version>
</dependency>
<!--单元测试的依赖-->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aspects</artifactId>
<version>5.2.3.RELEASE</version>
</dependency>
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis-spring</artifactId>
<version>2.0.4</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.1.10</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>5.2.3.RELEASE</version>
</dependency>
4.5 其他
log4j2
在二.6