unit10-mybatis框架

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-7ZIyKRxA-1611577268155)(第二阶段讲义04.assets/a0a2eb9fa872009998f7cd818925095d.png)]

课程计划:

1、MyBatis快速入门

2、MyBatis对数据库中数据的增删改查操作

3、#{}占位符的应用

4、动态SQL的应用

5、MyBatis的Mapper接口开发

MyBatis简介(了解)

什么是MyBatis

MyBatis 本是apache的一个开源项目iBatis,2010年这个项目由apache software foundation 迁移到了google code,并且改名为MyBatis 。2013年11月迁移到Github。

MyBatis是一个优秀的持久层框架,它对jdbc的操作数据库的过程进行封装,使开发者只需要关注SQL本身,而不需要花费精力去处理例如注册驱动、创建connection、创建statement、手动设置参数、结果集检索等jdbc繁杂的过程代码。

Mybatis通过xml或注解的方式将要执行的各种statement(statement、preparedStatemnt)配置起来,并通过java对象和statement中的sql进行映射生成最终执行的sql语句,最后由mybatis框架执行sql并将结果映射成java对象并返回。

总之,Mybatis对JDBC访问数据库的过程进行了封装,简化了JDBC代码,解决JDBC将结果集封装为Java对象的麻烦。

下图是MyBatis架构图:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-X4b5yxwa-1611577268165)(第二阶段讲义04.assets/f6d67734982f864da415a2a9cc4908a5.png)]

(1)mybatis-config.xml是Mybatis的核心配置文件,通过其中的配置可以生成SqlSessionFactory,也就是SqlSession工厂

(2)基于SqlSessionFactory可以生成SqlSession对象

(3)SqlSession是一个既可以发送SQL去执行,并返回结果,类似于JDBC中的Connection对象,也是Mybatis中至关重要的一个对象。

(4)Executor是SqlSession底层的对象,用于执行SQL语句

(5)MapperStatement对象也是SqlSession底层的对象,用于接收输入映射(SQL语句中的参数),以及做输出映射(即将SQL查询的结果映射成相应的结果)

为什么要使用MyBatis

思考:在开始之前,思考下如何通过JDBC查询Emp表中的所有记录,并封装到一个List集合中返回。(演示:准备数据、导包、导入JDBC程序)

1、使用传统方式JDBC访问数据库:

(1)使用JDBC访问数据库有大量重复代码(比如注册驱动、获取连接、获取传输器、释放资源等);

(2)JDBC自身没有连接池,会频繁的创建连接和关闭连接,效率低;

(3)SQL是写死在程序中,一旦修改SQL,需要对类重新编译;

(4)对查询SQL执行后返回的ResultSet对象,需要手动处理,有时会特别麻烦;

2、使用mybatis框架访问数据库:

(1)Mybatis对JDBC对了封装,可以简化JDBC代码;

(2)Mybatis自身支持连接池(也可以配置其他的连接池),因此可以提高程序的效率;

(3)Mybatis是将SQL配置在mapper文件中,修改SQL只是修改配置文件,类不需要重新编译。

(4)对查询SQL执行后返回的ResultSet对象,Mybatis会帮我们处理,转换成Java对象。

总之,JDBC中所有的问题(代码繁琐、有太多重复代码、需要操作太多对象、释放资源、对结果的处理太麻烦等),在Mybatis框架中几乎都得到了解决!!

MyBatis快速入门

准备数据,创建库和表

创建yonghedb库、emp表,并插入若干条记录

-- 1、创建数据库 yonghedb 数据库
create database if not exists yonghedb charset utf8;
use yonghedb; -- 选择yonghedb数据库
-- 2、删除emp表(如果存在)
drop table if exists emp;
-- 3、在 yonghedb 库中创建 emp 表
create table emp(id int primary key auto_increment,name varchar(50),job varchar(50),salary double
);
-- 4、往 emp 表中, 插入若干条记录
insert into emp values(null, '王海涛', '程序员', 3300);
insert into emp values(null, '齐雷', '程序员', 2800);
insert into emp values(null, '刘沛霞', '程序员鼓励师', 2700);
insert into emp values(null, '陈子枢', '部门总监', 4200);
insert into emp values(null, '刘昱江', '程序员', 3000);
insert into emp values(null, '董长春', '程序员', 3500);
insert into emp values(null, '苍老师', '程序员', 3700);
insert into emp values(null, '韩少云', 'CEO', 5000);

创建工程,导入所需jar包、创建测试类

1、创建Maven的java工程

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-afYXXi3f-1611577268168)(第二阶段讲义04.assets/image-20200927012330940.png)]

2、导入junit、mysql、mybaits、log4j等开发包

在pom.xml文件中引入相关依赖包即可

<dependencies><!-- junit单元测试 --><dependency><groupId>junit</groupId><artifactId>junit</artifactId><version>4.9</version></dependency><!-- mysql驱动 --><dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId><version>5.1.32</version></dependency><!-- mybatis --><dependency><groupId>org.mybatis</groupId><artifactId>mybatis</artifactId><version>3.2.8</version></dependency><!-- 整合log4j --><dependency><groupId>org.slf4j</groupId><artifactId>slf4j-log4j12</artifactId><version>1.6.4</version></dependency>
</dependencies>

3、创建com.tedu.mybatis.TestMybatis01测试类,并提供findAll方法(查询emp表中所有的员工信息),开发步骤如下:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-b6CgURKh-1611577268173)(第二阶段讲义04.assets/image-20200403151610907.png)]

/** 练习1(快速入门): 查询emp表中的所有员工, 返回一个List<Emp>集合
* @throws IOException */
@Test
public void findAll() throws IOException {//1.读取mybatis的核心配置文件(mybatis-config.xml)//2.通过配置信息获取一个SqlSessionFactory工厂对象//3.通过工厂获取一个SqlSession对象//4.通过namespace+id找到要执行的sql语句并执行sql语句//5.输出结果
}

添加mybatis-config.xml文件

1、在src/main/resources目录下,创建mybatis-config.xml文件(MyBatis的核心配置文件)

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-vwEOv5Jh-1611577268176)(第二阶段讲义04.assets/3e84b2af713f0ddea7c6164f57806c60.png)]

2、mybatis-config.xml文件配置如下:

mybatis-config文件头信息如下:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE configurationPUBLIC "-//mybatis.org//DTD Config 3.0//EN""http://mybatis.org/dtd/mybatis-3-config.dtd"><!-- MyBatis的全局配置文件 -->
<configuration ></configuration>

mybatis-config文件详细配置如下:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE configurationPUBLIC "-//mybatis.org//DTD Config 3.0//EN""http://mybatis.org/dtd/mybatis-3-config.dtd"><!-- MyBatis的全局配置文件 -->
<configuration ><!-- 1.配置环境,可配置多个环境(比如:develop开发、test测试) --><environments default="develop"><environment id="develop"><!-- 1.1.配置事务管理方式:JDBC/MANAGEDJDBC:将事务交给JDBC管理(推荐)MANAGED:自己管理事务--><transactionManager type="JDBC"></transactionManager><!-- 1.2.配置数据源,即连接池 JNDI/POOLED/UNPOOLEDJNDI:已过时POOLED:使用连接池(推荐)UNPOOLED:不使用连接池--><dataSource type="POOLED"><property name="driver" value="com.mysql.jdbc.Driver"/><property name="url" value="jdbc:mysql://localhost:3306/yonghedb?characterEncoding=utf-8"/><property name="username" value="root"/><property name="password" value="root"/></dataSource></environment></environments><!-- 2.导入Mapper配置文件,如果mapper文件有多个,可以通过多个mapper标签导入 --><mappers><mapper resource="EmpMapper.xml"/></mappers>
</configuration>

添加EmpMapper.xml文件

1、在src/main/resources目录下,创建EmpMapper.xml文件 (实体类的映射文件)

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-dmOgISmW-1611577268180)(第二阶段讲义04.assets/bf0e887dec06ea62dbad0673fe4b2730.png)]

2、EmpMapper.xml文件配置如下:

EmpMapper文件头信息如下:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapperPUBLIC "-//mybatis.org//DTD Mapper 3.0//EN""http://mybatis.org/dtd/mybatis-3-mapper.dtd"><!-- 不同Mapper文件的namespace值应该保证唯一在程序中通过[ namespace + id ]定位到要执行哪一条SQL语句-->
<mapper namespace=""></mapper>

EmpMapper文件详细配置如下:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapperPUBLIC "-//mybatis.org//DTD Mapper 3.0//EN""http://mybatis.org/dtd/mybatis-3-mapper.dtd"><!-- 不同Mapper文件的namespace值应该保证唯一在程序中通过[ namespace + id ]定位到要执行哪一条SQL语句-->
<mapper namespace="EmpMapper"><!-- 通过select、insert、update、delete标签声明要执行的SQL --><!-- 练习1: 查询emp表中的所有员工信息resultType指定查询的结果将会封装到什么类型中即使最终返回的结果是集合(List<Emp>),resultType也只需要指定集合中的泛型即可!--><select id="findAll" resultType="com.tedu.pojo.Emp">select * from emp</select></mapper>

添加并编写Emp实体类

注意:在当前实例中,Emp类中的属性和数据库表的字段名称必须一致,否则将会无法将结果集封装到Java对象中。

在src/main/java目录下创建 com.tedu.pojo.Emp类,并编辑Emp类:提供私有属性以及对应的getter方法、setter方法,并重写toString方法

package com.tedu.pojo;
/*** 实体类,用于封装Emp表中的一条用户信息*/
public class Emp {//1.声明实体类中的属性private Integer id;private String name;private String job;private Double salary;//2.提供对应的getter和setter方法public Integer getId() {return id;}public void setId(Integer id) {this.id = id;}public String getName() {return name;}public void setName(String name) {this.name = name;}public String getJob() {return job;}public void setJob(String job) {this.job = job;}public Double getSalary() {return salary;}public void setSalary(Double salary) {this.salary = salary;}//3.重写toString方法@Overridepublic String toString() {return "Emp [id=" + id + ", name=" + name + ", job=" + job + ", salary=" + salary + "]";}
}

实现测试类,并测试

1、实现findAll方法,代码如下:

/** 练习1(快速入门):  查询emp表中的所有员工, 返回一个List<Emp>集合* @throws IOException */
@Test
public void findAll() throws IOException {//1.读取mybatis的核心配置文件(mybatis-config.xml)InputStream in = Resources.getResourceAsStream("mybatis-config.xml");//2.通过配置信息获取一个SqlSessionFactory工厂对象SqlSessionFactory fac = new SqlSessionFactoryBuilder().build( in );//3.通过工厂获取一个SqlSession对象SqlSession session = fac.openSession();//4.通过namespace+id找到要执行的sql语句并执行sql语句List<Emp> list = session.selectList("EmpMapper.findAll");//5.输出结果for(Emp e : list) {System.out.println( e );}
}

2、执行findAll方法,输出结果为:

Emp [id=1, name=王海涛, job=程序员, salary=3300.0]
Emp [id=2, name=齐雷, job=程序员, salary=2800.0]
Emp [id=3, name=刘沛霞, job=程序员鼓励师, salary=2700.0]
Emp [id=4, name=陈子枢, job=部门总监, salary=4200.0]
Emp [id=5, name=刘昱江, job=程序员, salary=3000.0]
Emp [id=6, name=董长春, job=程序员, salary=3500.0]
Emp [id=7, name=苍老师, job=程序员, salary=3700.0]
Emp [id=8, name=韩少云, job=CEO, salary=5000.0]

MyBatis入门细节

mybatis-config.xml配置文件

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE configurationPUBLIC "-//mybatis.org//DTD Config 3.0//EN""http://mybatis.org/dtd/mybatis-3-config.dtd"><!-- MyBatis的全局配置文件 -->
<configuration ><!-- 1.配置环境,可配置多个环境(比如:develop开发、test测试) --><environments default="develop"><environment id="develop"><!-- 1.1.配置事务管理方式:JDBC/MANAGEDJDBC:将事务交给JDBC管理(推荐)MANAGED:自己管理事务--><transactionManager type="JDBC"></transactionManager><!-- 1.2.配置数据源,即连接池 JNDI/POOLED/UNPOOLEDJNDI:已过时POOLED:使用连接池(推荐)UNPOOLED:不使用连接池--><dataSource type="POOLED"><property name="driver" value="com.mysql.jdbc.Driver"/><property name="url" value="jdbc:mysql://localhost:3306/yonghedb?characterEncoding=utf-8"/><property name="username" value="root"/><property name="password" value="root"/></dataSource></environment></environments><!-- 2.导入Mapper配置文件,如果mapper文件有多个,可以通过多个mapper标签导入 --><mappers><mapper resource="EmpMapper.xml"/></mappers>
</configuration>

environments标签:该标签内部可以配置多个environment,即多种环境,每种环境可以做不同配置或连接不同数据库。例如,开发、测试、生产环境可能需要不同的配置,连接的数据库可能也不相同,因此我们可以配置三个environment,分别对应上面三种不同的环境。

但是要记住,environment可以配置多个,但是最终要使用的只能是其中一个!

environment标签:内部可以配置多种配置信息,下面介绍事务管理配置和数据源配置。

transactionManage标签:事务管理配置,mybatis中有两种事务管理方式,也就是

type="[JDBC|MANAGED]。

JDBC:这个配置就是直接使用了 JDBC的提交和回滚设置,它依赖于从数据源得到的连接来管理事务范围。推荐使用。
MANAGED:这个配置几乎没做什么。它从来不提交或回滚一个连接。需要自己手动添加并管理。不推荐使用。

dataSource标签:数据源,也就是连接池配置。这里type指定数据源类型,有三种内建的类型:JNDI、POOLED、UNPOOLED

JNDI:已过时,不推荐使用!
POOLED:使用连接池,mybatis会创建连接池,并从连接池中获取连接访问数据库,在操作完成后,将会把连接返回连池。
UNPOOLED:不使用连接池,该方式适用于只有小规模数量并发用户的简单应用程序上。

mappers标签:用于导入mapper文件的位置,其中可以配置多个mapper,即可以导入多个mapper文件。

EmpMapper.xml配置文件

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapperPUBLIC "-//mybatis.org//DTD Mapper 3.0//EN""http://mybatis.org/dtd/mybatis-3-mapper.dtd"><!-- 不同Mapper文件的namespace值应该保证唯一在程序中通过[ namespace + id ]定位到要执行哪一条SQL语句-->
<mapper namespace="EmpMapper"><!-- 通过select、insert、update、delete标签声明要执行的SQL --><!-- 练习1: 查询emp表中的所有员工信息resultType指定查询的结果将会封装到什么类型中即使最终返回的结果是集合(List<Emp>),resultType也只需要指定集合中的泛型即可!--><select id="findAll" resultType="com.tedu.pojo.Emp">select * from emp</select></mapper>

(1)第1行是xml的文档声明,用于声明xml的版本和编码

(2)第2、3、4行,引入了xml约束文档,当前xml文档将会按照mybatis-3-mapper.dtd文件所要求的规则进行书写。

(3)Mapper标签:根标签,其中namespace(名称空间,也叫命名空间),要求不能重复。在程序中通过【namespace + id 】定位到要执行哪一条SQL语句

(4)select标签:用于指定将来要执行的各种SQL语句。标签上可以声明属性,下面介绍常用的属性:id、resultType、resultMap

  • id属性:要求值不能重复。将来在执行SQL时,可以通过【namespace + id】找到指定SQL并执行。

  • resultType属性:从这条SQL语句中返回所期望类型的类的完全限定名称(包名+类名)。注意如果是集合情形,那应该是集合可以包含的类型,而不能是集合本身。

    简而言之,resultType控制查询SQL执行后返回值的类型或集合中的泛型,例如查询emp表中的单条记录,返回值是一个Emp对象,因此,resultType=“com.tedu.pojo.Emp”;

    如果查询emp表中的多条记录,返回值是一个List,此时resultType的值应该集合中的泛型,因此resultType=“com.tedu.pojo.Emp”;

  • resultMap属性:复杂对象结构(例如多表关联查询等)。 使用 resultType 或 resultMap,但不能同时使用。

MyBatis增删改查

新增员工

1、编辑EmpMapper.xml文件, 添加新增员工对应的sql.

<!-- 练习2: 新增员工信息: 赵云 保安 6000
增删改的标签上不用指定resultType, 因为返回值都是int类型
-->
<update id="insert" >insert into emp value(null, '赵云', '保安', 6000)
</update>

2、编写TestMybatis类,添加testInsert方法,实现新增员工操作。

/** 练习2: 新增员工信息: 赵云 保安 6000 */
@Test
public void testInsert() {//执行sql语句, 返回执行结果int rows = session.update("EmpMapper.insert");//提交事务session.commit();System.out.println("影响的行数: "+rows);
}

修改员工

1、编辑EmpMapper.xml文件, 添加新增员工对应的sql。

<!-- 练习3:修改员工信息:赵云 保镖 20000 -->
<update id="update">update emp set job='保镖', salary=20000 where name='赵云'
</update>

2、编写TestMybatis类,添加testUpdate方法,实现修改员工信息。

/** 练习3: 修改员工信息, 将赵云的job改为'保镖',salary改为20000 */
@Test
public void testUpdate() {//执行sql语句, 返回执行结果int rows = session.update("EmpMapper.update");//提交事务session.commit();System.out.println("影响行数:"+rows);
}

删除员工

1、编辑EmpMapper.xml文件, 添加新增员工对应的sql。

<!-- 练习4: 删除name为'赵云'的记录 -->
<update id="delete">delete from emp where name='赵云'
</update>

2、编写TestMybatis类,添加testDelete方法,实现删除员工。

/** 练习4: 删除name为'赵云'的记录 */
@Test
public void testDelete() {//执行sql语句, 返回执行结果int rows = session.update("EmpMapper.delete");//提交事务session.commit();System.out.println("影响行数:"+rows);
}

mybatis中的占位符

#{}占位符

在上面的增删改查操作中,SQL语句中的值是写死在SQL语句中的,而在实际开发中,此处的值往往是用户提交过来的值,因此这里我们需要将SQL中写死的值替换为占位符。

在mybatis中占位符有两个,分别是 #{} 占位符 和 ${} 占位符:

  • #{}:相当于JDBC中的问号(?)占位符,是为SQL语句中的参数值进行占位,大部分情况下都是使用#{}占位符;并且当#{}占位符是为字符串或者日期类型的值进行占位时,在参数值传过来替换占位符的同时,会进行转义处理(在字符串或日期类型的值的两边加上单引号);

    在mapper文件中: select * from emp where name=#{name}
    在程序执行时:     select * from emp where name=?
    参数:王海涛,将参数传入,替换占位符select * from emp where name=王海涛;    -- 错误select * from emp where name=‘王海涛’;  -- 正确
    
  • ${}:是为SQL片段(字符串)进行占位,将传过来的SQL片段直接拼接在 ${} 占位符所在的位置,不会进行任何的转义处理。(由于是直接将参数拼接在SQL语句中,因此可能会引发SQL注入攻击问题)

    需要注意的是:使用 ${} 占位符为SQL语句中的片段占位时,即使只有一个占位符,需要传的也只有一个参数,也需要将参数先封装再传递!

练习5:查询emp表中指定id的员工信息

在mapper文件中编写SQL语句:

<!-- 练习5: 查询emp表中指定id的员工信息 -->
<select id="findById" resultType="com.tedu.pojo.Emp">select * from emp where id=#{id}
</select>

Java代码实现:

/** 练习5: 查询emp表中指定id的员工信息 */
@Test
public void testFindById() {//执行sql语句, 返回执行结果Emp emp = session.selectOne( "EmpMapper.findById", 1 );System.out.println( emp );
}

练习6:新增员工信息: 张飞 Java开发工程师 15000

在mapper文件中编写SQL语句:

<!-- 练习6: 新增员工信息: 张飞 Java开发工程师 15000如果通过map集合传输参数, 需要保证占位符中的变量名和map集合中的key保持一致如果通过pojo对象传输参数, 需要保证占位符中的变量名和对象中的属性名保持一致, 或者在pojo中有对应的getXxx方法
-->
<update id="insert2">insert into emp values (null, #{name}, #{job}, #{salary})
</update>

Java代码实现:

/** 练习6: 新增员工信息: 张飞 Java开发工程师 15000 */
@Test
public void testInsert2() {//将要传输的参数封装到map集合中//Map map = new HashMap();//map.put("name", "张飞");//map.put("job", "Java开发工程师");//map.put("salary", 15000);//也可以将要传输的参数封装到Emp对象中Emp emp = new Emp();emp.setName("关羽123");emp.setJob("保安");emp.setSalary(8000.0);//执行sql语句intsession rows = session.update("EmpMapper.insert2", emp);//提交事务session.commit();System.out.println( "影响的行数: "+rows );
}

练习7:修改员工信息: 张飞 架构师 25000

在mapper文件中编写SQL语句:

<!-- 练习7: 修改员工信息: 张飞 架构师 25000 -->
<update id="update2">update emp set job=#{job}, salary=#{salary}where name=#{name}
</update>

Java代码实现:

/** 练习7: 修改员工信息: 张飞 架构师 25000 */
@Test
public void testUpdate2() {//将参数封装到Emp对象中Emp emp = new Emp();emp.setName("张飞");emp.setJob("架构师");emp.setSalary(25000.0);//执行sql语句intsession rows = session.update("EmpMapper.update2", emp);//提交事务session.commit();System.out.println("影响的行数: "+rows);
}

练习8:删除emp表中指定id的员工信息

mapper文件配置:

<!-- 练习8:删除emp表中指定id的员工信息 -->
<insert id="delete2" parameterType="String">delete from emp where id=#{id}
</insert>

java代码示例:

/*  练习8:删除emp表中指定id的员工信息 */
public void testDelete2() throws IOException{......//执行SQL语句int rows = session.delete("EmpMapper.delete2", 1);//提交事务session.commit();System.out.println("影响行数:"+rows);
}

在上面的增删改查练习中,当SQL语句中包含的参数值是传递过来的,在SQL语句中我们会通过#{}占位符进行占位,在SQL语句真正执行时,再将传递过来的值替换SQL语句中的占位符。

其实,#{}就是JDBC中的问号(?)占位符,因此为了安全考虑,在执行时会对传递过来的字符串和日期类型高的值进行转译处理。

例如:查询指定name的员工信息,SQL语句为:

select * from emp where name=#{name}

其实就等价于JDBC中: select * from emp where name=?

如果传过来的参数值为:王海涛,那么最终执行的SQL语句为:

-- 在参数替换占位符的同时进行了转义处理(在值的两边加上了单引号)
select * from emp where name='王海涛'

${}占位符

那么如果我们在传递的时候不是一个参数值,而是一个SQL片段呢?

例如:在查询时,我们想动态的传递查询的列:

select #{columns} from emp

此时传递过来的应该是一个SQL片段,不同于上面的参数值,如果此时还用#{},也会像上面一样被转译处理:select 'id,name,job' from emp,这不是我们希望看到的!

如果不想让传过来的SQL片段被转译处理,而是直接拼接在SQL语句中,那么这里可以使用${},例如:

select ${columns} from emp

拼接之后:select id,name,job from emp

练习9:动态指定要查询的列

在mapper文件中编写SQL语句:

<!-- 练习9: 动态指定要显示的列 -->
<select id="findAll2" resultType="com.tedu.pojo.Emp">select ${cols} from emp
</select>

java代码示例:

/** 练习9: 动态指定要查询的列 */
@Test
public void testFindAll2() {Map map = new HashMap();//map.put("cols", "id, name");//map.put("cols", "id, name, salary");map.put("cols", "id,name,job,salary");//执行sql语句, 返回结果List<Emp> list = session.selectList("EmpMapper.findAll2", map);//输出结果for ( Emp e : list ) {System.out.println( e );}
}

示例2: 根据name模糊查询emp表

在mapper文件中编写SQL语句:

<!-- 练习10: 根据name模糊查询emp表 -->
<select id="findAll3" resultType="com.tedu.pojo.Emp">select * from empwhere name like '%${name}%'
</select>
<!-- 练习11: 根据name模糊查询emp表 -->
<select id="findAll4" resultType="com.tedu.pojo.Emp">select * from empwhere name like #{name}
</select>

Java代码实现:

/*** 练习10: 根据name模糊查询emp表* '%王%' '%刘%'*/
@Test
public void testFindAll3() {//将参数封装到map集合中Map map = new HashMap();map.put("name", "涛");//执行sql, 返回结果List<Emp> list = session.selectList("EmpMapper.findAll3", map);//输出结果for (Emp emp : list) {System.out.println( emp );}
}
/*** 练习11: 根据name模糊查询emp表* '%王%' '%刘%'*/
@Test
public void testFindAll4() {//将参数封装到map集合中Map map = new HashMap();map.put("name", "%刘%");//执行sql, 返回结果List<Emp> list = session.selectList("EmpMapper.findAll4", map);//输出结果for (Emp emp : list) {System.out.println( emp );}
}

需要注意的是,在传递 ${} 对应的值时,即使只有一个参数,也需要将值存入map集合中!!

总结:在大多数情况下还是使用#{}占位符,而${}多用于为SQL片段进行占位!

动态SQL标签

if、where标签

  • <if>标签:是根据test属性中的布尔表达式的值,从而决定是否执行包含在其中的SQL片段。如果判断结果为true,则执行其中的SQL片段;如果结果为false,则不执行其中的SQL片段

  • <where>标签:用于对包含在其中的SQL片段进行检索,在需要时可以生成where关键字,并且在需要时会剔除多余的连接词(比如and或者or)

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-SJRGeGPh-1611577268183)(第二阶段讲义04.assets/f06a2a2de234a540b189b15d65943989.png)]

练习12:根据薪资查询员工信息(if标签)

  • <if>标签:是根据test属性中的布尔表达式的值,从而决定是否执行包含在其中的SQL片段。如果判断结果为true,则执行其中的SQL片段;如果结果为false,则不执行其中的SQL片段

在mapper文件中编写SQL语句:

<!-- 练习12: 根据薪资查询员工信息
* 如果没有参数, 则不执行where子句, 默认查询所有员工:
*   select * from emp
* 如果参数中只有minSal(即minSal不为null), 则:
*   ... where salary > minSal
* 如果参数中只有maxSal(即maxSal不为null), 则:
*   ... where salary < maxSal
* 如果参数有 minSal、maxSal(即minSal、maxSal不为null), 则:
*       ... where salary > minSal and salary < maxSal  -->
<select id="findAllBySal" resultType="com.tedu.pojo.Emp">select * from empwhere 1=1<if test="minSal != null">and salary>#{minSal}</if><if test="maxSal != null">and salary <![CDATA[ < ]]> #{maxSal}</if>
</select>

Java代码实现:

/* 练习12: 根据薪资查询员工信息
* 如果没有参数, 则不执行where子句, 默认查询所有员工:
*   select * from emp
* 如果参数中只有minSal(即minSal不为null), 则:
*   ... where salary > minSal
* 如果参数中只有maxSal(即maxSal不为null), 则:
*   ... where salary < maxSal
* 如果参数有 minSal、maxSal(即minSal、maxSal不为null), 则:
*   ... where salary > minSal and salary < maxSal  */
@Test
public void testFindBySal() {Map<String, Object> map = new HashMap<String, Object>();//map.put( "minSal" , 3000 );//map.put( "maxSal", 4000 );List<Emp> list = session.selectList( "EmpMapper.findBySal", map );for (Emp emp : list) {System.out.println( emp );}
}

练习13:根据薪资查询员工信息(where标签)

  • <where>标签:用于对包含在其中的SQL片段进行检索,在需要时可以生成WHERE关键字,并且在需要时会剔除多余的连接词(比如and或者or)
<!-- 练习13: 根据薪资查询员工信息
* 如果没有参数, 则不执行where子句, 默认查询所有员工:
*   select * from emp
* 如果参数中只有minSal(即minSal不为null), 则:
*   ... where salary > minSal
* 如果参数中只有maxSal(即maxSal不为null), 则:
*   ... where salary < maxSal
* 如果参数有 minSal、maxSal(即minSal、maxSal不为null), 则:
*       ... where salary > minSal and salary < maxSal  -->
<select id="findAllBySal2" resultType="com.tedu.pojo.Emp">select * from emp<where><if test="minSal != null">and salary>#{minSal}</if><if test="maxSal != null">and salary <![CDATA[ < ]]> #{maxSal}</if></where>
</select>

Java代码实现:

/**
* 练习13: 根据薪资查询员工信息
* 如果没有参数, 则不执行where子句, 默认查询所有员工:
*   select * from emp
* 如果参数中只有minSal(即minSal不为null), 则:
*   ... where salary > minSal
* 如果参数中只有maxSal(即maxSal不为null), 则:
*   ... where salary < maxSal
* 如果参数有 minSal、maxSal(即minSal、maxSal不为null), 则:
*   ... where salary > minSal and salary < maxSal  */
@Test
public void testFindAllBySal() {//封装参数到map集合中Map map = new HashMap();map.put("minSal", 3000);map.put("maxSal", 4000.0);List<Emp> list = session.selectList("EmpMapper.findAllBySal2", map);for (Emp emp : list) {System.out.println( emp );}
}

foreach元素

foreach标签:可以对传过来的参数数组或集合进行遍历,以下是foreach标签上的各个属性介绍:

属性 属性描述
item 必需,若collection为数组或List集合时,item表示其中的元素,若collection为map中的key,item表示map中value(集合或数组)中的元素
open 可选,表示遍历生成的SQL片段以什么开始,最常用的是左括号’(’
collection 必需,值为遍历的集合类型,例如:如果参数只是一个数组或List集合,则collection的值为array或list;如果传的是多个参数,用map封装,collection则指定为map中的key。
close 可选,表示遍历生成的SQL片段以什么结束,最常用的是右括号’)’
separator 可选,每次遍历后给生成的SQL片段后面指定间隔符

练习14: 根据员工的id批量删除员工信息

在mapper文件中编写SQL语句:

<!-- 练习14: 根据员工的id批量删除员工信息delete from emp where id in (1,3,5,7)collection: 如果传的参数仅仅是一个数组或者List集合, collection可以指定为array或list; 如果传的是多个参数,用map封装,collection则指定为map中的keyopen: 指定生成的SQL片段以什么符号开始close: 指定生成的SQL片段以什么符号结束item: 指定变量接收数组或集合中的元素separator: 指定一个间隔符, 在将数组或集合中的每个元素拼接到SQL片段之后, 在后面拼接一个间隔符-->
<delete id="deleteByIds">delete from emp where id in<foreach collection="array" open="(" item="id" separator="," close=")">#{id}</foreach>
</delete>

Java代码实现:

/* 练习14: 根据员工的id批量删除员工信息 */
@Test
public void testDeleteByIds() {//获取所要删除的员工的id数组Integer[] ids = {1,3,5,7};int rows = session.delete( "EmpMapper.deleteByIds", ids );System.out.println( "影响行数: "+rows );
}

练习15:根据员工的id批量更新员工信息

在mapper文件中编写SQL语句:

<!-- 练习15: 根据员工的id批量更新员工信息将id为 2、4、6、8的员工的薪资在原有基础上增加1000update emp set salary=salary + 1000where id in(2,4,6,8);
-->
<update id="updateByIds">update emp set salary=salary + #{sal}where id in<foreach collection="arrIds" open="(" item="id" separator="," close=")">#{id}</foreach>
</update>

Java代码实现:

/* 练习15: 根据员工的id批量更新员工信息* 将id为 2、4、6、8的员工的薪资在原有基础上增加1000*/
@Test
public void testUpdateByIds() {Integer[] ids = {2,4,6,8}; //获取所要更新的员工的id数组Double sal = 1000.0; //要涨的薪资//传递的参数超过1个, 将参数封装到map集合中再传递Map<String, Object> map = new HashMap<String, Object>();map.put( "arrIds" , ids );map.put( "sal", sal );int rows = session.update( "EmpMapper.updateByIds", map );System.out.println( "影响行数: "+rows );
}

下面练习自己完成:

/* 练习16:根据员工的id批量查询指定id的员工信息* 查询id为 2、4、6、8的员工的信息*/
@Test
public void testFindByIds() {}

Mapper接口开发

Mapper接口开发介绍

在上面的Mybatis案例中, 通过SqlSession对象调用方法进行增删改查操作时,方法中需要传入的第一个参数是一个字符串值,该值对应的内容为: (Mapper文件中的)namespace + id, 通过这种方式,找到Mapper文件中映射的SQL语句并执行!!

这种方式由于传入的是字符串值, 很容易发生字符串拼写错误且编译时期不会提示。

这里我们将会讲解比上面更加简单的方式,也是我们企业开发中最常用的方式,即使用mapper接口开发。使用mapper接口开发需要注意以下几点:

1、创建一个接口,接口的全限定类名和mapper文件的namespace值要相同
2、mapper文件中每条要执行的SQL语句,在接口中要添加一个对应的方法,并且接口中的方法名和SQL标签上的id值相同
3、Mapper接口中方法接收的参数类型,和mapper.xml中定义的sql的接收的参数类型要相同
4、接口中方法的返回值类型和SQL标签上的resultType即返回值类型相同(如果方法返回值是集合,resultType只需要指定集合中的泛型)

Mapper接口开发实现

下面将使用mapper接口开发的方式,实现根据id查询指定的员工信息

1、创建com.tedu.dao.EmpMapper接口

由于接口的全路径名(com.tedu.dao.EmpMapper)要和EmpMapper.xml的namespace值保持一致,因此, 这里将namespace的值改为com.tedu.dao.EmpMapper:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapperPUBLIC "-//mybatis.org//DTD Mapper 3.0//EN""http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.tedu.dao.EmpMapper"></mapper>

2、在接口中提供findById方法

/**
* 根据id查询员工信息
* @param id
* @return Emp
*/
public Emp findById(Integer id);

注意:方法的名字要和映射的sql标签的id值保持一致

方法的返回值类型和resultType的类型要一致

<!-- 1.查询:查询Emp表中指定id的员工信息 -->
<select id="findById" resultType="com.tedu.pojo.Emp">select * from emp where id=#{id}
</select>

3、提供实现类,测试Emp接口中的根据id查询员工的方法

(1)创建com.tedu.test.TestMybatisInf类, 并提供testFindById方法

public class TestMybatisInf {@Testpublic void testFindById() throws Exception{}
}

4、实现testFindById方法并测试

@Test
public void testFindById() throws Exception{......//3.获取Mapper接口对象EmpMapper map = session.getMapper(EmpMapper.class);//4.调用接口对象的方法进行查询Emp e = map.findById(2);//5.输出结果System.out.println(e);
}

5、在接口中提供findAll方法

/**
* 查询所有的员工信息
* @return List<Emp>
*/
public List<Emp> findAll();

注意:方法的名字要和映射的sql标签的id值保持一致

方法的返回值类型和resultType的类型要一致, 例如:

<!-- 2.查询Emp表中所有员工的信息 -->
<select id="findAll" resultType="com.tedu.pojo.Emp">select * from emp
</select>

6、提供实现类,测试Emp接口中的查询所有员工的方法

(1)创建com.tedu.test.TestMybatisInf类, 并提供testFindAll方法

public class TestMybatisInf {...@Testpublic void testFindAll () throws Exception{}
}

实现testFindAll方法并测试

@Test
public void testFindAll() throws Exception{...//3.获取Mapper接口对象EmpMapper map = session.getMapper(EmpMapper.class);//4.调用接口对象的方法进行查询List<Emp> list = map.findAll();//5.输出结果for (Emp e : list) {System.out.println(e);}
}

几个可以优化的地方

加入log4j日志框架

在项目中加入log4j的配置文件,用于打印日志信息,便于开发调试。

在src(或相似的目录)下创建log4j.properties如下:

# Global logging configuration
log4j.rootLogger=DEBUG, stdout
# Console output...
log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern=%5p [%t] - %m%n

mybatis默认使用log4j作为输出日志信息。

只要将该文件放在指定的位置,log4j工具会自动到指定位置加载上述文件,读取文件中的配置信息并使用!

SQL语句中的特殊符号

示例:添加一个查询功能:查询薪资小于3500的所有员工。

1、编辑EmpMapper.xml文件, 添加查询对应的sql.

<!-- 练习12: 根据薪资查询员工信息 -->
<select id="findBySal" resultType="com.tedu.pojo.Emp">select * from empwhere 2=2 <if test="minSal != null">and salary >= #{minSal}</if><if test="maxSal != null">and salary <= #{maxSal}</if>
</select>

2、但在书写完后,xml文件提示有错误:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-BAqV3VgU-1611577268187)(第二阶段讲义04.assets/image-20200407172154514.png)]

原来,小于号(<)在xml文件中是特殊字符,被xml文件当成了标签的开始符号。

3、解决方法:可以使用 &lt;代替 <,例如:

<select id="findBySal" resultType="com.tedu.pojo.Emp">select * from empwhere 2=2 <if test="minSal != null">and salary >= #{minSal}</if><if test="maxSal != null">and salary &lt;= #{maxSal}</if>
</select>

或者是将特殊符号包含在CDATA区( <![CDATA[ ]]> )中,这是因为放在CDATA区中的内容,只会被xml解析器当作普通文本来处理。而不是被当成标签的一部分处理。

<!-- 查询薪资小于3500的所有员工 -->
<select id="findBySal" resultType="com.tedu.pojo.Emp">select * from empwhere 2=2 <if test="minSal != null">and salary >= #{minSal}</if><if test="maxSal != null">and salary <![CDATA[ <= ]]> #{maxSal}</if>
</select>

jdbc.properties文件

在开发中,通常我们会将连接数据库的配置信息单独放在一个properties文件中(方便管理和维护),
然后在MyBatis的mapper文件中引入properties文件的配置信息即可!

1、在src目录下创建一个名称为jdbc.properties的文件

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-weiR23Nu-1611577268190)(第二阶段讲义04.assets/c689eb39f8139c834ce014974d446229.png)]

2、jdbc.properties文件内容如下:

jdbc.driver=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/yonghedb?characterEncoding=utf-8
jdbc.username=root
jdbc.password=root

3、在mybatis-config.xml文件中引入jdbc.properties文件

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-1Ks6gCay-1611577268192)(第二阶段讲义04.assets/7f967dea070cc2df6038dc0a11fcfc7e.png)]

1、其中 <properties resource="jdbc.properties"/>标签用于引入jdbc.properties文件,默认到classpath即类目录下寻找指定的文件;

2、properties标签上value属性中配置的 ${jdbc.xxx}:

${jdbc.driver}:其实就是jdbc.properties文件中的 jdbc.driver的值,即:

com.mysql.jdbc.Driver

${jdbc.url}:其实就是jdbc.properties文件中的 jdbc.url的值,即:

jdbc:mysql://localhost:3306/mybatisdb?characterEncoding=utf-8

${jdbc.username}:其实就是jdbc.properties文件中的 jdbc.username的值,即:

root

${jdbc.password}:其实就是jdbc.properties文件中的 jdbc.password的值,即:

root

扩展内容

Jdbc回顾

通过JDBC查询Emp表中的所有记录,并封装到一个List集合中返回

1、创建TestJdbc类,完成查询所有员工:

package com.tedu.jdbc;import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.ArrayList;
import java.util.List;
import com.tedu.pojo.Emp;
/** Jdbc回顾 */
public class TestJdbc {public static void main(String[] args) {/* 查询emp表中的所有员工信息,将每个员工信息的封装到一个* Emp对象中,再将封装了员工信息所有Emp对象存入List集合* 中,并遍历输出所有的员工信息 */List<Emp> empList = findAll();for(Emp emp : empList){System.out.println(emp);}}/*** 查询emp表中的所有员工信息,封装到List集合并返回*/private static List<Emp> findAll() {Connection conn = null;Statement stat = null;ResultSet rs = null;try {//1.注册数据库驱动Class.forName("com.mysql.jdbc.Driver");//2.获取数据库连接(Connection)conn = DriverManager.getConnection("jdbc:mysql:///yonghedb", "root", "root");//3.获取传输器stat = conn.createStatement();//4.利用传输器发送sql到数据库执行,并返回执行结果String sql = "select * from emp";rs = stat.executeQuery(sql);//5.处理结果//5.1.声明List集合,用于封装所有的员工信息List<Emp> empList = new ArrayList();//5.2.遍历ResultSet结果集while(rs.next()) {//5.3.获取结果集中的每一条员工信息int id = rs.getInt("id");String name = rs.getString("name");String job = rs.getString("job");double salary = rs.getDouble("salary");//5.4.将每一条员工信息封装到一个Emp对象中Emp emp = new Emp();emp.setId(id);emp.setName(name);emp.setJob(job);emp.setSalary(salary);//5.5.将Emp对象存入List集合中empList.add(emp);}return empList;} catch (Exception e) {e.printStackTrace();System.out.println("查询失败!");} finally{//6.释放资源if(rs != null){try {rs.close();} catch (SQLException e) {e.printStackTrace();}finally{rs = null;}}if(stat != null){try {stat.close();} catch (SQLException e) {e.printStackTrace();}finally{stat = null;}}if(conn != null){try {conn.close();} catch (SQLException e) {e.printStackTrace();}finally{conn = null;}}}return null;}
}

2、声明Emp实体类,用于封装员工信息:

package com.tedu.pojo;
/*** 实体类,用于封装Emp表中的一条用户信息*/
public class Emp {//1.声明实体类中的属性private Integer id;private String name;private String job;private Double salary;//2.提供对应的getter和setter方法public Integer getId() {return id;}public void setId(Integer id) {this.id = id;}public String getName() {return name;}public void setName(String name) {this.name = name;}public String getJob() {return job;}public void setJob(String job) {this.job = job;}public Double getSalary() {return salary;}public void setSalary(Double salary) {this.salary = salary;}//3.重写toString方法@Overridepublic String toString() {return "Emp [id=" + id + ", name=" + name + ", job=" + job + ", salary=" + salary + "]";}
}

mybatis-config文件没有提示的解决办法

如果在没有网络(外网)的情况下,编写mybatis-config.xml文件没有提示,可以按照下面的步骤进行配置:

(1)找到mybatis-3-config.dtd的文件的位置,例如:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-TEI4RLfi-1611577268194)(第二阶段讲义04.assets/065e6b731aa74d5650821fdc63cbd4b9.png)]

(2)复制下面的url地址:

http://mybatis.org/dtd/mybatis-3-config.dtd

(3)在eclipse菜单栏中: window --> Preferences --> 在搜索框中搜索 [ xml ] XML --> XML Catalog --> User Specified Entries --> Add…

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-vKE7VDxj-1611577268196)(第二阶段讲义04.assets/eef68594c0d7b072e09702b3d6289831.png)]

(4)在弹出的窗口中:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-dtwcYOah-1611577268198)(第二阶段讲义04.assets/364a97a96fcb74e6ca14d4a9c8b89e6b.png)]

Mapper文件没有提示的解决办法

如果在没有网络(外网)的情况下,编写XxxMapper.xml文件没有提示,可以按照下面的步骤进行配置:

(1)找到mybatis-3-mapper.dtd的文件的位置,例如:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-RQmJ5TQt-1611577268201)(第二阶段讲义04.assets/50f2e9357bd4d42791f85098a75795c0.png)]

(2)复制上面的url地址,即:

http://mybatis.org/dtd/mybatis-3-mapper.dtd

(3)在eclipse菜单栏中: window --> Preferences --> 在搜索框中搜索 [ xml ] XML --> XML Catalog --> User Specified Entries --> Add…

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-CHyBjAwK-1611577268203)(第二阶段讲义04.assets/eef68594c0d7b072e09702b3d6289831.png)]

(4)在弹出的窗口中:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-HBEUB5Vt-1611577268206)(第二阶段讲义04.assets/e907b64d04d56389acba7c7e2f6bd74c.png)]

配置达内XML Schema代理服务器

1、打开Eclipse的配置首选项

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-gnxFq46P-1611577268210)(第二阶段讲义04.assets/eb81455caf93624c289a4e60105b6aca.png)]

2、找到 XML -> XML Catalog, 添加配置项目:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-41tTwvHZ-1611577268212)(第二阶段讲义04.assets/bf2d8978925006db94220c283a695f57.png)]

3、添加URI代理配置, 比如:配置Spring Schema

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-iUQB54RX-1611577268215)(第二阶段讲义04.assets/7e5343736b72ca605ac7e577025ccd9e.png)]

4、可以配置的代理有

http://ibatis.apache.org http://doc.tedu.cn
http://www.springframework.org http://doc.tedu.cn
http://mybatis.org http://doc.tedu.cn
http://hibernate.sourceforge.net, http://www.hibernate.org http://doc.tedu.cn
http://struts.apache.org http://doc.tedu.cn

mybatis作业

04-03作业:

理解什么是事务?

掌握事务四大特性(原子性、一致性、隔离性、持久性)

自己独立完成mybatis快速入门程序!!

04-07作业:

将课上讲过的mybatis练习自己再做一遍!

unit11-Spring框架

Spring简介

什么是Spring?

Spring框架(简称Spring)是一个用于简化Java企业级应用开发的开源应用程序框架,以IoC(Inverse Of Control:控制反转/反转控制)和AOP(Aspact Oriented Programming:面向切面编程)为核心,提供了表现层SpringMVC和持久层Spring
JDBC以及业务层事务管理等众多模块的企业级应用技术,还能整合开源世界中众多著名的第三方框架和类库,逐渐成为使用最多的JavaEE企业应用开源框架。

SSH(struts2 Spring hibernate)

SSM(Springmvc Spring mybatis)

Spring的本质是管理软件中的对象,即创建对象维护对象之间的关系

Spring的发展历程

1997 年 IBM提出了EJB 的思想

1998 年,SUN制定开发标准规范 EJB1.0

1999 年,EJB1.1 发布

2001 年,EJB2.0 发布

2003 年,EJB2.1 发布

2006 年,EJB3.0 发布

Rod Johnson (罗德·约翰逊,Spring 之父)

Expert One-to-One J2EE Development without EJB(2004)

阐述了 J2EE 开发不使用 EJB的解决方式(Spring 雏形)

2017年9月份发布了Spring的最新版本Spring 5.0通用版

Spring的优势

1)方便解耦,简化开发

通过 Spring提供的 IoC容器,可以将对象间的依赖关系交由Spring进行控制,避免硬编码所造成的过度程序耦合。用户也不必再为较为底层的需求编写代码,可以更专注于上层的应用。

2)AOP 编程的支持

通过 Spring的 AOP 功能,方便进行面向切面的编程,许多不容易用传统OOP(Object Oriented Programming:面向对象编程) 实现的功能可以通过 AOP 轻松应付。

3)声明式事务的支持

可以将我们从单调烦闷的事务管理代码中解脱出来,通过声明式方式灵活的进行事务的管理,提高开发效率和质量。

4)方便程序的测试

可以用非容器依赖的编程方式进行几乎所有的测试工作,测试不再是昂贵的操作,而是随手可做的事情。

5)方便集成各种优秀框架

Spring可以降低各种框架的使用难度,提供了对各种优秀框架(Struts、Hibernate、Hessian、Quartz等)的直接支持。

6)降低 JavaEE API 的使用难度。

Spring对 JavaEE API(如 JDBC、JavaMail、远程调用等)进行了薄薄的封装层,使这些API 的使用难度大为降低。

。。。

Spring的架构

Spring 的目标就是要整合一切优秀资源,然后对外提供一个统一的服务。Spring 模块构建在核心容器之上,核心容器定义了创建、配置和管理 bean 的方式,如下图所示:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-nPttvC19-1611577268219)(第二阶段讲义04.assets/bf0676465991176183ba7d0765728e57.png)]

Spring框架由多个模块组成,它们根据应用开发功能进行分组。下面列出了Spring框架中的各个模块组,并描述了其中一些重要模块组所提供的功能。

模块
Core container 包含构成Spring框架基础的模块。该组中的spring-core和spring-beans模块提供了Spring的DI功能和IoC容器实现。spring-expressions模块在Spring应用中通过Spring表达式语言配置应用程序提供了支持。
AOP and instrumentation 包含支持AOP(面向切面编程)和类工具模块。该组中的spring-aop模块提供Spring的AOP功能,spring-instrument模块提供了对类工具的支持
Messaging 包含了简化开发基于消息的应用的spring-messaging模块
Data Access / Integration 包含简化与数据库和消息提供者交互的模块。spring-jdbc模块简化了用JDBC与数据库狡猾的过程。spring-orm模块提供了与ORM(对象关系映射)框架的继承,如JPA和Hibernate。spring-jms模块简化了与JMS提供者的狡猾,此模块组还包含spring-tx模块,该模块提供了编程式与声明式事务管理
Web 包含简化开发Web和portlet应用的模块。spring-web和spring-webmvc模块都是用于开发Web应用和RESTful的Web服务的。spring-websocket模块支持使用WebSocket开发Web应用。
Test 包含spring-test模块,简化了创建单元和集成测试。

Spring模块之间的依赖关系如下图所示:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-qwFjVshj-1611577268221)(第二阶段讲义04.assets/image-20200628231857502.png)]

Spring IoC容器

什么是控制反转

IoC(Inverse Of Control)控制反转:即,把创建对象的权利交给框架,也就是指将对象的创建、对象的初始化、对象的存储、对象的管理交给了Spring容器。

IoC 是一种通过描述来生成或者获取对象的技术,对于Java初学者 更多时候所熟悉的是使用 new 关键字来创建对象,而在spring中则不是,它是通过描述(XML或注解)来创建对象。

在 Spring 中把每一个需要管理的对象称之为 Spring Bean(简称为Bean),而Spring中管理这些 Bean 的容器,被我们称之为 Spring IoC 容器(简称 IoC容器)


[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-4T5wwgM8-1611577268225)(第二阶段讲义04.assets/0a11c44a30f5a7328589e4031d624ffd.png)]


在此之前,当需要对象时,通常是利用new关键字创建一个对象:

/* 创建一个User对象——这里使用new对象的方式造成了程序之间的耦合性提升 */
User u = new User();

但由于new对象,会提高类和类之间的依赖关系,即代码之间的耦合性。

而现在我们可以将对象的创建交给框架来做:

// 获取Spring IoC容器对象
ApplicationContext ac = new ClassPathXmlApplicationContext("beans.xml");
// 从spring的IoC容器中获取User对象
User user = (User)ac.getBean("user");

将需要的POJO提前在配置文件(XML或注解)中进行描述,Spring会根据配置文件将其中所描述生成 IoC容器并装配Bean,在需要使用时,再次获取即可

这样一来,当需要对象时不用自己创建,而是通过框架获取即可,不用硬编码去new对象,自然就降低类和类之间的依赖关系,也就是耦合性。

Spring IoC入门

在Spring中允许我们通过XML或注解方式装配Bean,下面先来介绍Spring中通过XML方法如何装配Bean。

创建工程,引入依赖

1、创建Maven的Java工程:CGB-SPRING-01

2、引入junit、Spring的jar包:在maven工程的pom.xml文件的根标签(project)内添加如下配置:

<dependencies><!-- 添加junit依赖 --><dependency><groupId>junit</groupId><artifactId>junit</artifactId><version>4.10</version></dependency><!-- 添加spring的依赖 --><dependency><groupId>org.springframework</groupId><artifactId>spring-context</artifactId><version>4.3.12.RELEASE</version></dependency>
</dependencies>

导入后保存pom文件,项目如图所示:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-fWmLhBne-1611577268228)(第二阶段讲义04.assets/image-20200927005056589.png)]

创建Spring配置文件

1、首先定义一个User类:com.tedu.pojo.User.java,代码如下:

public class User { }

2、在工程的src/main/resources源码目录下,创建Spring核心配置文件—beans.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"xsi:schemaLocation="http://www.springframework.org/schema/beanshttp://www.springframework.org/schema/beans/spring-beans.xsd"></beans>

3、在 beans.xml 文件中添加如下配置:将User类作为Bean装配到Spring IoC容器中

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://www.springframework.org/schema/beanshttp://www.springframework.org/schema/beans/spring-beans.xsd"><!-- 将User类作为bean装配到Spring IoC容器中(即User类的实例由Spring创建) --><bean id="user" class="com.tedu.pojo.User"></bean></beans>

以下是关于spring应用程序XML文件的要点:

每个<bean>元素配置一个由Spring IoC容器所管理的应用程序对象。在Spring框架的术语中,一个<bean>元素代表一个Bean。id属性指定bean的唯一名称,class属性指定bean的完全限定类名。

创建类进行测试

1、创建测试类—— com.tedu.IoCTest

2、测试步骤及代码如下:

public class IoCTest {@Testpublic void testIoC() {/* 获取Spring的IoC容器:*    将XML配置文件 bean.xml 传递给 ClassPathXmlApplicationContext*   的构造方法,Spring会读取其中的配置,将里面配置的Bean对象装配到IoC容器中*/ApplicationContext ctx = new ClassPathXmlApplicationContext("beans.xml");// 使用getBean方法获取对应的POJO并输出User user = (User)ctx.getBean( "user" );System.out.println( user );}
}

3、运行结果:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-BwJoNqbi-1611577268232)(第二阶段讲义04.assets/image-20200926215657931.png)]

4、Spring小结:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-B9Jwl8PC-1611577268235)(第二阶段讲义04.assets/image-20200926231333716.png)]

1)在程序运行后,Spring会到配置文件中读取 Bean 配置信息,根据配置信息生产 Spring IoC容器对象

2)Spring IoC 容器根据读取到的Bean配置信息,到硬盘上加载相应的 Class 字节码文件,根据反射创建 Bean 实例。

3)Spring会将创建的Bean存储到 Spring 容器中,也就是所谓的 Bean Pool(Bean池,底层是一个Map)中

4) 根据 Spring 提供的 getBean 方法获取Bean实例并应用到我们的应用程序中。

自己试一试:定义一个com.tedu.pojo.UserInfo类,并将UserInfo作为 Bean 装配到 Spring IoC容器中,在程序中通过 getBean 方法获取 UserInfo 实例,并输出到控制台。

几个方法

下面列出了BeanFactory接口的几个常用方法,可以在程序中试一试。

/* 根据bean的名称,返回指定bean的一个实例(如果有) */
Object getBean(String name) throws BeansException;/* 根据Bean的类型(也可以是父类或父接口),返回唯一匹配给定对象类型的bean实例(如果有) */
<T> T getBean(Class<T> requiredType) throws BeansException;/* 检索这个Bean工厂是否包含一个给定名称的bean实例  */
boolean containsBean(String name);/* 获取Bean的类型  */
Class<?> getType(String name) throws NoSuchBeanDefinitionException;/* 根据Bean的名称,检索当前Bean是不是单例,如果是则返回true */
boolean isSingleton(String name) throws NoSuchBeanDefinitionException;/* 根据Bean的名称,检索当前Bean是不是多例,如果是则返回true */
boolean isPrototype(String name) throws NoSuchBeanDefinitionException;

Bean的作用范围

Bean对象的作用范围

在Spring容器中管理的Bean对象的作用域,可以通过scope属性或用相关注解指定其作用范围。

<bean id="user" scope="singleton" class="com.tedu.pojo.User"></bean>

1、Bean的scope属性常用取值:

(1)scope="singleton":单实例(默认值)
(2)scope="prototype":多实例

singleton(单例)或prototype(多例)其含义如下:

1) singleton:单实例(默认),这个作用域标识的对象具备全局唯一性。

a)当把一个 bean 定义设置 scope 为singleton作用域时,那么Spring IoC容器只会创建该bean定义的唯一实例。也就是说,整个Spring IoC容器中只会创建当前类的唯一一个对象。
b)这个单一实例会被存储到 bean池中,并且所有针对该 bean 的后续请求和引用都将返回被缓存的、唯一的这个对象实例。
c)单例的bean对象由spring负责创建、初始化、存储、销毁(在spring容器销毁时销毁)

2) prototype:多实例。这个作用域标识的对象每次获取都会创建新的对象。

当把一个 bean 定义设置 scope 为prototype作用域时,Spring IoC容器会在每一次获取当前Bean时,都会产生一个新的Bean实例(相当于new的操作),该实例不会被存储到bean池中,spring也不负责销毁,当程序不再使用这个对象时,由GC负责销毁。

2、什么时候使用单例和多例?

1)从使用频次上考虑,如果一个对象使用的频率非常高,建议使用单例,这样会将bean对象存储到bean池中,从始至终只有一个实例,可以减少对象创建,减少对资源的消耗。

2)在没有线程安全问题的前提下,没必要每次获取都创建一个对象,这样子既浪费CPU又浪费内存;

3)从使用频次上考虑,如果一个对象使用的频率非常低,没有必要将对象存储到map中(存储到bean池中的对象会一直存在bean池中,在spring容器销毁时销毁),建议使用多例。

4)如果一个对象被多个线程所共享,并且对象中存在共享数据,一旦这个数据被多个线程所操作,就可能会产生线程不安全问题,可以考虑使用多例

在Spring中配置Bean实例是单例还是多例方法是:

单例:

<bean id="user" scope="singleton" class="com.tedu.pojo.User"></bean>

多例:

<bean id="user" scope="prototype" class="com.tedu.pojo.User"></bean>

3、Bean对象的生命周期

生命周期:指一个对象何时创建、何时销毁以及从创建之后到销毁之前的所处的状态

单实例对象(singleton):

出生:当spring容器对象创建时,bean对象就会被创建
活着:只要容器没有销毁,bean对象就会一直存活
死亡:当spring容器销毁,bean对象也会跟着消亡总结:单例对象的生命周期和容器相同,spring容器负责singleton对象的创建、存储、销毁(随着spring容器销毁而销毁)。

多实例对象(prototype):

出生:当获取bean对象时,spring框架才会为我们创建bean对象
活着:只要对象是在使用过程中,就会一直存活
死亡:当对象长时间不用,且没有别的对象引用时,由Java垃圾回收机制负责回收总结:spring容器只负责prototype对象的创建和初始化,不负责存储和销毁。当对象长时间不用时,由Java垃圾回收机制负责回收

Spring DI依赖注入

两种注入方式

DI(Dependency Injection)依赖注入 。

依赖注入,即组件之间的依赖关系由容器在应用系统运行期来决定,也就是由容器动态地将某种依赖关系的目标对象实例注入到应用系统中的各个关联的组件之中。


简单来说,所谓的依赖注入其实就是,在创建对象的同时或之后,如何给对象的属性赋值。

如果对象由我们自己创建,这一切都变得很简单,例如:

User user = new User();
user.setName("韩少云"); //通过setName方法为name属性赋值
user.setAge(18); //通过setAge方法为age属性赋值

或者:

User user = new User("韩少云", 18); //在创建对象的同时,通过有参构造函数为对象的属性赋值

如果对象由Spring IoC容器创建,那么Spring是怎么给属性赋值的?Spring提供两种方式为属性赋值:

1)set方法注入

2)构造方法注入

set方法注入

顾名思义,就是在类中提供需要注入成员的 set 方法。spring框架底层会调用set方法为成员变量赋值。

例如:Spring框架负责创建User的Bean对象之后,会通过调用User对象的setName方法为name属性赋值。

普通属性注入

需求:通过Spring IoC容器获取User实例,并为User对象的name和age属性(普通属性)赋值

1、在User类中声明name和age属性,并添加对应的setter和getter方法,以及重写toString方法

public class User {//提供私有成员变量private String name;private Integer age;private UserInfo info;//提供getter和setter方法...//重写toString方法...}

2、在beans.xml中声明User类的bean实例

<!-- 将User类作为bean装配到Spring IoC容器中(即User类的实例由Spring创建) -->
<bean id="user" class="com.tedu.pojo.User"></bean>

3、创建测试类—DITest

package com.tedu;
import org.Springframework.context.support.ClassPathXmlApplicationContext;public class DITest {@Testpublic void testDI() {// 获取Spring的IoC容器ApplicationContext ctx = new ClassPathXmlApplicationContext("beans.xml");// 使用getBean方法获取对应的POJO并输出User user = (User)ctx.getBean( "user" );System.out.println( user );}
}

由于这里没有为User对象的属性赋值,所以此时运行测试,结果为:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-4QmJAfZb-1611577268238)(第二阶段讲义04.assets/image-20200927001829961.png)]

4、修改beans.xml中User Bean标签,为User Bean的name和age属性注入属性值

<!-- 将User类作为bean装配到Spring IoC容器中(即User类的实例由Spring创建) -->
<bean id="user" class="com.tedu.pojo.User"><!-- 通过set方法为User Bean的name、age属性赋值 --><property name="name" value="韩少云"/><property name="age" value="20"/>
</bean>

上面的bean定义表明,User bean通过<property>标签为User对象的nameage属性赋值。

之所以称之为是set方法注入,是因为在框架底层会调用User对象的setter方法为属性赋值。

例如:下面User bean标签中的<property>标签是为User对象的属性赋值。

<property name="name" value="韩少云"></property>

因为 name="name",因此需要User对象中有setName方法才可以完成注入,否则将会注入失败。

value="韩少云",表示为(User对象的)setName方法对应的属性赋值为"韩少云"。

普通属性(基本数据类型和String类型)直接通过value注入即可。

5、运行测试类DITest,结果为:

上面通过Spring提供的set方法对User对象的属性进行了赋值,所以此时运行测试,结果为:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-PsZtilZZ-1611577268241)(第二阶段讲义04.assets/image-20200927002049957.png)]

对象属性注入

需求:通过Spring IoC容器获取User实例,并为User对象的userInfo属性(对象属性)赋值

1、在beans.xml中,将UserInfo类作为bean装配到Spring IoC容器中(如果已有可以忽略)

<!-- 将UserInfo类作为bean装配到Spring IoC容器中(即UserInfo类的实例由Spring创建) -->
<bean id="userInfo" class="com.tedu.pojo.UserInfo"></bean>

2、在beans.xml中,将通过 Spring 容器获取的UserInfo对象作为值,注入到User对象的userInfo属性

<!-- 将User类作为bean装配到Spring IoC容器中(即User类的实例由Spring创建) -->
<bean id="user" class="com.tedu.pojo.User"><!-- 通过set方法为User Bean的name、age属性赋值 --><property name="name" value="韩少云"/><property name="age" value="20"/><!-- 通过set方法为User Bean的info(对象)属性赋值 --><property name="info" ref="userInfo"/>
</bean><!-- 将UserInfo类作为bean装配到Spring IoC容器中(即UserInfo类的实例由Spring创建) -->
<bean id="userInfo" class="com.tedu.pojo.UserInfo"></bean>

由于此处是将UserInfo对象的引用作为值设置给info属性,因此ref属性指定为UserInfo对象bean标签的id值。将通过 Spring 容器获取的UserInfo对象传递给setUserInfo方法,从而对User对象的属性赋值。

对象属性通过ref属性注入。

3、运行测试类DITest,结果为:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-pl8qGhGA-1611577268244)(第二阶段讲义04.assets/image-20200927003430730.png)]

构造方法注入

顾名思义,就是使用类中的构造函数,为成员变量赋值——即构造方法注入。

需要注意的是:为成员变量赋值是通过配置的方式,让spring框架在创建bean对象的同时,为成员变量赋值。而不是我们自己去实现。

需求:通过Spring IoC容器获取User实例,并为User对象的name、age、info属性赋值

1、为User类添加无参构造和有参构造函数

 //提供无参构造public User() { }//提供有参构造public User(String name, Integer age, UserInfo info) {this.name = name;this.age = age;this.info = info;}

注意:如果不添加有参构造函数,可以无参、有参构造函数都不添加;

如果要添加有参构造函数,强烈建议同时添加无参构造函数,否则在很多地方使用时都可能会抛出异常!

2、修改beans.xml文件,将set方法修改为构造方法注入。

<!-- 将User类作为bean装配到Spring IoC容器中(即User类的实例由Spring创建) -->
<bean id="user" class="com.tedu.pojo.User"><!-- 通过set方法为User Bean的name、age属性赋值 <property name="name" value="韩少云"/><property name="age" value="20"/> --><!-- 通过set方法为User Bean的info(对象)属性赋值<property name="info" ref="userInfo"/> --><!-- 通过构造方法为User Bean的name、age、info属性赋值 --><constructor-arg name="name" value="马云"/><constructor-arg name="age" value="30"/><constructor-arg name="info" ref="userInfo"/></bean><!-- 将UserInfo类作为bean装配到Spring IoC容器中(即UserInfo类的实例由Spring创建) -->
<bean id="userInfo" class="com.tedu.pojo.UserInfo"></bean>

其中,constructor-arg标签name属性的值必须和构造函数中参数的名字相同!

同样的,普通属性直接通过value注入即可;对象属性通过ref属性注入。

3、运行测试类DITest,结果为:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-sHOXZYkN-1611577268246)(第二阶段讲义04.assets/image-20200927004148317.png)]

Spring全注解开发

由于在后面的Spring Boot课程当中,因为 Spring Boot是基于注解的开发Spring IoC,所以我们下面将会介绍Spring的全注解开发,为后续为Spring Boot的学习打下基础。

下面我们来看一个最为简单的例子。

Spring注解开发入门

1、创建Maven的Java工程:CGB-SPRING-02

2、首先定义一个User类:com.tedu.pojo.User.java,代码如下:

public class User {//提供私有成员变量private String name;private Integer age;//提供getter和setter方法...//重写toString方法...}

3、再定义一个Java配置文件:com.tedu.AppConfig.java,代码如下:

/* @Configuration注解:*    告诉spring这是一个java配置文件(类),spring会根据这个文件生成IoC容器*    AppConfig(java配置类)== beans.xml配置文件*/
@Configuration
public class AppConfig {/* @Bean注解:将当前方法返回的POJO装配到IoC容器中(作用等同于bean标签)*    name属性用于定义这个bean的名称,如果没有配置将会默认使用方法名作为名字*  类型为方法的返回值类型*/@Bean(name="user")public User getUser() {User user = new User();user.setName("刘德华");user.setAge(20);return user;}}

这里需要注意以下两个注解的作用:

@Configuration注解:告诉spring这是一个java配置文件(类),spring会根据这个java文件生成IoC容器

@Bean注解:将当前方法返回的对象装配到IoC容器中(作用等同于bean标签),括号中的name属性用于定义这个bean的名称,如果没有配置name,将会默认使用这个方法的名字作为bean的名称保存到Spring的IoC容器中。

4、基于AnnotationConfigApplicationContext获取Spring的IoC容器

提供测试类:com.tedu.IoCTest,代码如下:

public class IoCTest {@Testpublic void testAnnoTation01() {/* 获取Spring的IoC容器:*   将Java配置文件AppConfig传递给 AnnotationConfigApplicationContext*   的构造方法,读取其中的配置,将里面配置的Bean对象装配到IoC容器中*/ApplicationContext ctx = new AnnotationConfigApplicationContext( AppConfig.class );// 使用getBean方法获取对应的POJO并输出User user = (User)ctx.getBean( "user" );System.out.println( user );}
}

运行输出结果如下:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-iHmMdRZN-1611577268248)(第二阶段讲义04.assets/image-20200926204813755.png)]

显然,在 AppConfig.java 文件中名称为User的Bean已经被装配到IoC容器中,并且可以通过 getBean 方法获取到该 Bean,并将 Bean 的属性信息打印出来。

当然这只是一个很简单的方法,@Bean 注解也不是唯一创建 Bean 的方法,还有其他的方式可以让 IoC 容器装配 Bean,并且 Bean 之间还有依赖关系需要进一步处理,这些会在下面进行讲解。

通过扫描装配Bean

如果所有的bean都是通过@Bean注解装配到 Spring IoC容器中,那将是一件很麻烦的事情。在Spring中允许我们进行扫描装配Bean到IoC容器。扫描装配Bean使用的注解是 @Component 和 ComponentScan。

@Component注解:标记哪个类需要被扫描装配到 Spring IoC容器

@ComponentScan注解:标记采用何种策略去扫描装配Bean(默认扫描当前类所在的包及其子包)

扫描装配Bean示例

1、定义一个 com.tedu.UserDao 和一个 com.tedu.UserService,在类上添加 @Component注解

package com.tedu.dao;import org.springframework.stereotype.Component;/* @Component注解:用于标记此类会被Spring IoC容器扫描配置为Bean* @Component("userDao"): 其中配置的“userDao”将作为Bean的名称,也可以不指定*        如果不指定,IoC容器会将类名第一个字母小写,其余不变作为bean的名称 */
@Component("userDao")
public class UserDao {}
package com.tedu.service;import org.springframework.stereotype.Component;@Component
public class UserService {}

@Component注解:用于标记此类会被Spring IoC容器扫描配置为Bean,其中配置的“userDao”将作为Bean的名称,也可以不指定。如果不指定,IoC容器会将类名第一个字母小写,其余不变作为bean的名称

2、扫描装配Bean:为了让Spring IoC容器装配这个UserDao,改造AppConfig

/* * @ComponentScan注解:让Spring进行扫描,默认只扫描此类所在的包及其子包* @ComponentScan("com.tedu.*"):也可以指定所扫描的包(com.tedu包及其子包)* @ComponentScan(basePackages = {"com.tedu.dao","com.tedu.service"}):*    当需要扫描的包有多个时,可以指定多个包。*/
@Configuration
@ComponentScan
public class AppConfig {}

@ComponentScan注解:让Spring进行扫描,默认只扫描此类所在的包及其子包,也可以指定所扫描的包和类,例如:

1)@ComponentScan( "com.tedu.*" ):其中的 “com.tedu.*” 表示扫描com.tedu包及其所有子包,例如:com.tedu.dao、com.tedu.service、com.tedu.pojo都是com.tedu的子包,都会被扫描到。如果需要扫描的包有多个,可以按照下面的方式指定。

2) @ComponentScan( basePackages={"com.tedu.dao","com.tedu.service"} ):basePackages指定一个数组,数组中定义了所需要扫描的包。{“com.tedu.dao”,“com.tedu.service”} 表示扫描com.tedu.dao和com.tedu.service这两个包 。

3)@ComponentScan( basePackageClasses={UserDao.class,UserService.class} ):basePackageClasses指定一个数组,数组中定义了所要扫描类的字节码对象。

3、添加测试方法:

/* 2、扫描装配Bean */
@Test
public void testAnnoTation02() {/* 获取Spring的IoC容器:*  将Java配置文件AppConfig传递给 AnnotationConfigApplicationContext*   的构造方法,读取其中的配置,将里面配置的Bean对象装配到IoC容器中*/ApplicationContext ctx = new AnnotationConfigApplicationContext( AppConfig.class );// 获取IoC容器中的所有bean的名称,并输出String[] beanNames = ctx.getBeanDefinitionNames();for (String beanName : beanNames) {System.out.println( beanName );}
}

@Scope和@Lazy注解

上面在通过XML方式配置时说过,可以通过bean标签上的 scope 属性 指定bean的作用域

<bean id="user" scope="singleton" class="com.tedu.pojo.User"></bean>

在使用注解开发中,可以通过@Scope注解达到相同的效果

其中@Scope注解:指定当前Bean是单例作用域还是多例作用域,默认是单例

示例:

/* @Component注解:用于标记此类会被Spring IoC容器扫描配置为Bean* @Component("userDao"): 其中配置的“userDao”将作为Bean的名称,也可以不指定*         如果不指定,IoC容器会将类名第一个字母小写,其余不变作为bean的名称* @Scope注解:指定bean的作用域,如果不指定,默认是单例,可以指定为 prototype */
@Component("userDao")
@Scope("prototype")
public class UserDao {}

其中@Lazy注解:延迟加载(懒加载),这个是配合singleton Bean使用的,对prototype Bean不生效。

当为Bean开启了懒加载策略后,如果不获取Bean实例,Spring IoC容器将不会创建该实例的,在第一次获取时,Spring IoC容器才会创建该实例。

应用场景:大对象,使用频率不高的对象,建议使用此策略。

示例:

/* @Component注解:用于标记此类会被Spring IoC容器扫描配置为Bean* @Component("userDao"): 其中配置的“userDao”将作为Bean的名称,也可以不指定*         如果不指定,IoC容器会将类名第一个字母小写,其余不变作为bean的名称* @Scope注解:指定bean的作用域,如果不指定,默认是单例,可以指定为 prototype* @Lazy注解:对此Bean使用懒加载策略,在第一次获取时,IoC容器才会创建该实例*     true为默认值,表示使用懒加载,可以省略 */
@Component("userDao")
@Scope("singleton")
@Lazy(true)
public class UserDao {}

属性赋值(@Value)

1、通过@Value注解可以为Bean的属性赋值

1)例如:为User对象的name属性和age属性赋值

@Component
public class User {//提供私有成员变量@Value("赵云")private String name;@Value("24")private Integer age;...
}

2)测试:获取User的Bean实例并输出到控制台查看

/** 1、测试@Value注解*   @Value注解:为Bean的属性赋值(值:数值,字符串,布尔值)*/
@Test
public void testValue(){// 获取Spring的IoC容器ApplicationContext ctx = new AnnotationConfigApplicationContext( AppConfig.class );// 使用getBean方法获取对应的POJO并输出User user = (User)ctx.getBean( "user" );System.out.println( user );
}

3)运行结果为:可以看出已经成功为User对象的name、age属性赋了值

User [name=赵云, age=24]

2、也可以将属性值写到配置文件中,再通过@Value注解及${}符为Bean的属性赋值,例如:

1)在项目src/main/resource/下提供config.properties文件,添加两行配置:

config.name=马云
config.age=28

2)在AppConfig配置类中,通过@PropertySource注解读取config.properties文件中的key/value到运行环境中

/** @PropertySource注解:读取外部配置文件中的key/value,并保存到运行环境中*/
@PropertySource("classpath:/config.properties")
@Configuration
@ComponentScan
public class AppConfig {...
}

3)修改User类中的代码,通过@Value注解及${}符为Bean的属性赋值

@Component
public class User {//提供私有成员变量@Value("${config.name}")private String name;@Value("${config.age}")private Integer age;...
}

4)测试:可以看出已经成功为User对象的name、age属性赋了值

User [name=马云, age=28]

自动装配(@Autowired)

@Autowired是我们使用得最多的注解之一,可以将定义好的Bean作为属性值注入到其他Bean的属性上,而这一过程是自动完成的。

@Autowired自动注入的原则:

1)默认优先按照类型从Spring容器中进行查找bean,如果找到一个则直接注入,如果没有找到,则抛出NoSuchBeanDefinition异常。

2)但如果该类型的bean对象在spring容器中有多个,此时还会基于属性名进行匹配,如果属性名和spring中bean的名字相同,则直接注入,如果都不匹配则直接抛出NoUniqueBeanDefinition异常。

3)当然,我们可以通过@Qualifier注解,显式的为属性指定要注入哪一个名字的bean(此注解必须配合@AutoWired注解使用)。

1、下面测试@Autowired注解的使用

1)修改User类中的代码:添加UserInfo属性(通过@Autowired注解为注入值),并提供对应的getter和setter方法,最后重写toString方法

@Component
public class User {//提供私有成员变量...@Autowiredprivate Animal animal;//提供getter和setter方法....public Animal getAnimal() { return animal; }public void setAnimal(Animal animal) { this.animal = animal; }//重写toString方法@Overridepublic String toString() {return "User [name=" + name + ", age=" + age + ", animal=" + animal + "]";}
}

2)添加Animal接口和Dog类,让Dog实现Animal接口,再通过@Component注解将Dog作为bean装配到spring容器中:

package com.tedu.pojo;
//Animal接口
public interface Animal { }
package com.tedu.pojo;
import org.springframework.stereotype.Component;
//Dog实现类
@Component("dog")
public class Dog implements Animal{ }

3)测试:

/** 2、测试@Autowired注解*   @Autowired注解:将定义好的bean作为属性值注入到其他Bean的属性上*/
@Test
public void testAutowired(){// 获取Spring的IoC容器ApplicationContext ctx = new AnnotationConfigApplicationContext( AppConfig.class );// 使用getBean方法获取对应的POJO并输出User user = (User)ctx.getBean( "user" );System.out.println( user );
}

测试结果如下:

User [name=马云, age=28, animal=com.tedu.pojo.Dog@55040f2f]

从打印结果可以看出,@Autowired注解成功为Animal类型的animal属性注入了一个Dog类型的Bean。底层是根据animal属性的类型(Animal)到Spring容器中寻找该类型的bean,如果刚好找到一个,就可以完成注入。

2、那么,如果UserInfo类型的bean对象在spring容器中有多个,@AutoWired注解该如何注入?

1)再添加一个Cat类并实现Animal接口,再通过@Component注解将Cat作为bean装配到spring容器中:

package com.tedu.pojo;
import org.springframework.stereotype.Component;@Component("cat")
public class Cat implements Animal{}

2)此时Animal类型的bean在Spring容器中有两份(bean的name分别为:dog和cat),再次运行测试,程序会抛出如下异常:

...
Caused by: org.springframework.beans.factory.NoUniqueBeanDefinitionException: No qualifying bean of type 'com.tedu.pojo.Animal' available: expected single matching bean but found 2: cat,dog
...

解决方法1:可以将User类中的animal属性名改为其中一个bean的名字(dog或cat);或者将其中一个bean的名字改为User类中的animal属性名,再运行测试。

解决方法2:也可以通过 @Qualifier注解显式的为属性指定要注入哪一个名字的bean

 @Autowired@Qualifier("dog")private Animal animal;

解决方法3:还可以通过@Primary注解指定默认首选的Bean。即注入依赖的过程中,当有多个候选者的时候,可以指定哪个候选者为主要的候选者。

unit12-Springmvc框架

MVC设计模式

什么是设计模式

设计模式(Design Pattern)是一套被反复使用、多数人知晓的、经过分类的、代码设计经验的总结。

使用设计模式的目的:为了代码可重用性、让代码更容易被他人理解、保证代码可靠性。

设计模式使代码编写真正工程化;

设计模式是软件工程的基石脉络,如同大厦的结构一样。

设计模式就是一种模子,经过多年实践锤炼形成一套行之有效的完成某个特定任务的步骤和方式。

例如:西凤酒的酿造过程,酿造工序,前后不能变,温差不能变,这样做就是好喝,稍微改动就变味道了。

再如,北京烤鸭,就是那样做,就是那些调料腌制,变量配比,味道口感就是不如人家。

MVC设计模式

MVC设计模式是一种通用的软件编程思想

在MVC设计模式中认为, 任何软件都可以分为三部分组成:

(1)控制程序流转的控制器(Controller

(2)封装数据处理数据的模型(Model

(3)负责展示数据的视图(view

并且在MVC设计思想中要求一个符合MVC设计思想的软件应该保证上面这三部分相互独立,互不干扰,每一个部分只负责自己擅长的部分。

如果某一个模块发生变化,应该尽量做到不影响其他两个模块。这样做的好处是,软件的结构会变得更加的清晰,可读性强。有利于后期的扩展和维护,并且代码可以实现复用。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-6woMmWbr-1611577268251)(第二阶段讲义04.assets/20e60e831a34d3a4a68805e386aa1779.png)]

初识SpringMVC

Servlet的缺点

1、通常情况下,一个Servlet类只负责处理一个请求,若项目中有成百上千个请求需要处理,就需要有成百上千个Servlet类,这样会使得项目中Servlet类的个数暴增;

2、在Servlet3.0版本之前,每一个Servlet都需要在web.xml文件中至少做八行配置信息,配置内容多且繁琐。当Servlet特别多时,web.xml配置量太多,不利于团队开发;

3、当通过客户端提交参数到服务器,通过Servlet进行接收时,无论数据本身是什么格式,在Servlet中一律按照字符串进行接收,后期需要进行类型转换,复杂类型还需要特殊处理,特别麻烦!

String value = request.getParameter(String name);

4、servlet具有容器依赖性,必须放在服务器中运行,不利于单元测试;

SpringMVC简介

Springmvc是Spring框架的一个模块,Spring和Springmvc无需中间整合层整合

Springmvc是一个基于mvc的web框架

Springmvc执行原理

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-k4wXbVZM-1611577268253)(第二阶段讲义04.assets/14c0c2f53620fb0e29c11170729226f0.png)]

(1).用户发送请求 至 前端控制器(DispatcherServlet);

提示:DispatcherServlet的作用:接收请求,调用其它组件处理请求,响应结果,相当于转发器、中央处理器,是整个流程控制的中心

(2).前端控制器(DispatcherServlet)收到请求后调用处理器映射器(HandlerMapping)

处理器映射器(HandlerMapping)找到具体的Controller(可以根据xml配置、注解进行查找),并将Controller返回给DispatcherServlet;

(3).前端控制器(DispatcherServlet)调用处理器适配器(HandlerAdapter)。处理器适配器经过适配调用具体的Controller;(Controller–> service --> Dao --> 数据库)

Controller执行完成后返回ModelAndView,

提示:Model(模型数据,即Controller处理的结果,Map) View(逻辑视图名,即负责展示结果的JSP页面的名字)

处理器适配器(HandlerAdapter)将controller执行的结果(ModelAndView)返回给前端控制器(DispatcherServlet);

(4).前端控制器(DispatcherServlet)将执行的结果(ModelAndView)传给视图解析器(ViewReslover)

视图解析器(ViewReslover)根据View(逻辑视图名)解析后返回具体JSP页面

(5).前端控制器(DispatcherServlet)根据Model对View进行渲染(即将模型数据填充至视图中);

前端控制器(DispatcherServlet)将填充了数据的网页响应给用户。

其中整个过程中需要开发人员编写的部分有ControllerServiceDaoView;

Springmvc入门案例

需求:

(1)通过浏览器访问 http://localhost:8080/项目名称/hello 地址,在控制台输出 “hello Springmvc”

(2)将请求转向(跳转到) /WEB-INF/pages/home.jsp 页面

创建Maven—web工程

1、通过Maven创建web工程

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-17IpgqFB-1611577268255)(第二阶段讲义04.assets/image-20200410140300719.png)]

2、在pom.xml中引入Springmvc所需jar包:将下面的配置直接拷贝到pom.xml中的根标签内

<dependencies><!-- 单元测试 --><dependency><groupId>junit</groupId><artifactId>junit</artifactId><version>4.10</version><scope>test</scope></dependency><!-- springMVC的jar包 --><dependency><groupId>org.springframework</groupId><artifactId>spring-webmvc</artifactId><version>4.1.3.RELEASE</version></dependency><!-- servlet 和 jsp的jar包 --><dependency><groupId>javax.servlet</groupId><artifactId>servlet-api</artifactId><version>2.5</version><scope>provided</scope></dependency><dependency><groupId>javax.servlet</groupId><artifactId>jsp-api</artifactId><version>2.0</version><scope>provided</scope></dependency><!-- java对象转换json的工具类<dependency><groupId>com.fasterxml.jackson.core</groupId><artifactId>jackson-databind</artifactId><version>2.5.1</version></dependency> --></dependencies>

在web.xml中配置前端控制器

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xmlns="http://java.sun.com/xml/ns/javaee"xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"id="WebApp_ID" version="2.5"><!-- 配置springmvc前端控制器, 将所有请求交给Springmvc来处理 --><servlet><servlet-name>springmvc</servlet-name><servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class><!-- 配置springmvc核心配置文件的位置,默认springmvc的配置文件是在WEB-INF目录下,默认的名字为springmvc-servlet.xml,如果要放在其他目录,则需要指定如下配置:--><init-param><param-name>contextConfigLocation</param-name><param-value>classpath:springmvc-config.xml</param-value></init-param>       </servlet><!-- 其中的斜杠(/)表示拦截所有请求(除JSP以外), 所有请求都要经过Springmvc前端控制器 --><servlet-mapping><servlet-name>springmvc</servlet-name><url-pattern>/</url-pattern></servlet-mapping></web-app>

创建并配置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:mvc="http://www.springframework.org/schema/mvc"xmlns:context="http://www.springframework.org/schema/context"xsi:schemaLocation="http://www.springframework.org/schema/mvchttp://www.springframework.org/schema/mvc/spring-mvc-4.0.xsdhttp://www.springframework.org/schema/beanshttp://www.springframework.org/schema/beans/spring-beans-4.0.xsdhttp://www.springframework.org/schema/contexthttp://www.springframework.org/schema/context/spring-context-4.0.xsd"><!-- 1.配置前端控制器放行静态资源(html/css/js等,否则静态资源将无法访问) --><mvc:default-servlet-handler/><!-- 2.配置注解驱动,用于识别注解(比如@Controller) --><mvc:annotation-driven></mvc:annotation-driven><!-- 3.配置需要扫描的包:spring自动去扫描 base-package 下的类,如果扫描到的类上有 @Controller、@Service、@Repository、@Component等注解,将会自动将类注册为bean --><context:component-scan base-package="com.tedu.controller"></context:component-scan><!-- 4.配置内部资源视图解析器prefix:配置路径前缀suffix:配置文件后缀..并且可以跳转到 /WEB-INF/pages/test.jsp 页面--><bean class="org.springframework.web.servlet.view.InternalResourceViewResolver"><property name="prefix" value="/WEB-INF/pages/"/><property name="suffix" value=".jsp"/></bean></beans>

创建并实现HelloController类

1、创建com.tedu.controller.HelloController类

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-z6H9GGrV-1611577268258)(第二阶段讲义04.assets/image-20200411120818696.png)]

2、实现HelloController类

package com.tedu.controller;
import org.Springframework.stereotype.Controller;
import org.Springframework.web.bind.annotation.RequestMapping;/* @Controller作用: 表示当前类属于controller层*    同时标识将当前类的对象的创建交给Spring容器负责*     http://localhost/day16-Springmvc/hello*/
@Controller
public class HelloController {/* @RequestMapping("/hello") 用于配置当前方法的访问路径,不能省略,且不能重复!* @RequestMapping注解在当前方法上声明的访问路径, 在当前controller类中不能重复!* 如果controller类上没有访问路径,当前方法上的访问路径在所有controller类中都不能重复!*/@RequestMapping("/hello") public String testHello() {System.out.println( "hello Springmvc...." );//跳转到哪一个路径    /WEB-INF/pages/home.jspreturn "home";}
}

创建并实现home.jsp

在WEB-INF/pages/目录下,创建home.jsp页面。

WEB-INF/pages/home.jsp

<%@ page pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html><head><meta charset="UTF-8">
</head>
<body><h1>day16-Springmvc...home.jsp....</h1>
</body>
</html>

访问测试

打开浏览器,输入url地址:http://localhost:8080/day16-Springmv/hello 地址,访问结果如下:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-QXRCJcIn-1611577268261)(第二阶段讲义04.assets/image-20200411120516046.png)]

Springmvc参数绑定

当项目中引入Springmvc框架后,所有的请求流转将由Springmvc进行控制,当客户端发送的请求中包含数据(也就是请求参数)时,那么该如何在controller层获取这些参数呢?

Springmvc会自动的将请求中包含的参数和方法的参数进行匹配,也就是说只要保证,请求中的参数名称和方法中的参数名称相对应(另,参数的格式也要正确),在方法中就可以使用这些参数—即请求中的参数。

基本类型参数绑定

当需要获取客户端发送过来的少量数据时,可以在Controller中声明的方法上,通过声明方法参数对这些参数一个一个进行接收,具体示例如下:

需求:通过浏览器发请求访问Controller,并在请求中携带name、age数据访问服务器,在服务器端的
Controller中获取这些数据。

1、在HelloController类中添加testParam1方法,用于接收基本类型的参数,代码实现如下:

package com.tedu.controller;
import org.Springframework.stereotype.Controller;
import org.Springframework.web.bind.annotation.RequestMapping;@Controller /* 这个注解表示当前类是属于控制层 */
public class HelloController {/* 1、测试Springmvc的简单类型参数绑定* ../testParam1?name=张飞&age=20&addr=河北* 如何获取请求中name、age、addr的参数值?*     request.getParameter("name") -- 张飞* 在方法上添加三个同类型、同名的形参分别为 name、age、addr* 用于接收请求中name、age、addr参数的值  */@RequestMapping("/testParam1")public String testParam1(String name, Integer age, String addr) {System.out.println( "name="+name );System.out.println( "age="+age );System.out.println( "addr="+addr );return "home";}
}

2、访问HelloController中的testParam1方法,在访问时,注意将name、age、addr参数一起发送给服务器:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ehIEw4WX-1611577268264)(第二阶段讲义04.assets/image-20200411121233000.png)]

控制台输出结果为:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-zh7AxOcL-1611577268266)(第二阶段讲义04.assets/image-20200411121322974.png)]

包装类型参数绑定

当需要获取客户端发送过来的多个数据时,可以在Controller中声明的方法上,通过声明方法参数对这些数据一个一个进行接收较麻烦,可以在方法上声明对象类型的参数,通过对这些数据统一进行接受,Springmvc会自动将接收过来的参数封装在对象中,具体示例如下:

1、在HelloController类中添加param2方法,用于接收对象类型的参数,代码实现如下:

package com.tedu.controller;
import org.Springframework.stereotype.Controller;
import org.Springframework.web.bind.annotation.RequestMapping;@Controller /* 这个注解表示当前类是属于控制层 */
public class HelloController {/* 2、测试Springmvc的包装类型参数绑定* ../testParam2?name=关羽&age=30&addr=北京* 如何获取请求中name、age、addr的参数值?* 提供一个User类,在类中添加和请求参数同名的属性* 底层通过调用setName、setAge、setAddr方法将参数值封装到User对象中 */@RequestMapping("/testParam2")public String testParam2(User user) {System.out.println( "user.name="+user.getName() );System.out.println( "user.age="+user.getAge() );System.out.println( "user.addr="+user.getAddr() );return "home";}
}

3、创建User类,声明name、age、addr属性,提供对应的set和get方法

package com.tedu.pojo;/*** 封装用户信息*/
public class User {private String name;private Integer age;private String addr;public User() {} //无参构造函数public User(String name, Integer age, String addr) {super();this.name = name;this.age = age;this.addr = addr;}//get、set方法public String getName() {return name;}public void setName(String name) {this.name = name;}public Integer getAge() {return age;}public void setAge(Integer age) {this.age = age;}public String getAddr() {return addr;}public void setAddr(String addr) {this.addr = addr;}
}

4、访问HelloController中的param2方法,在访问时,注意将name和age参数一起发送给服务器:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-z1JAUm60-1611577268269)(第二阶段讲义04.assets/image-20200411125305873.png)]

控制台输出结果为:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-GygCq3kK-1611577268271)(第二阶段讲义04.assets/image-20200411125340557.png)]

日期类型参数绑定

1、在HelloController类中添加testParam3方法,代码实现如下:

@Controller /* 这个注解表示当前类是属于控制层 */
public class HelloController {  /* 3、测试Springmvc的日期类型参数绑定* ../testParam3?date=2020-4-10 16:40:39   报400错误,表示参数类型不匹配* ../testParam3?date=2020/4/10 16:40:39*   如何获取请求中date参数的值?*    Springmvc默认是以斜杠接收日期类型的参数, 如果提交的日期就是以斜杠*     分隔的, Springmvc就可以接收这个日期类型的参数, 否则将无法接收*  如果提交的日期参数就是以横杠分隔, 也可以修改Springmvc默认的接收格式*    改为以横杠分隔!!*/@RequestMapping("/testParam3")public String testParam3(Date date) {System.out.println( "date="+date );return "home";}
}

2、访问HelloController中的testParam3方法,在访问时,注意将date参数一起发送给服务器:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-bVhnUuHi-1611577268275)(第二阶段讲义04.assets/image-20200927012759682.png)]

控制台输出结果为:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-gVgzSoGn-1611577268277)(第二阶段讲义04.assets/image-20200927012814880.png)]

常见问题:

1、当访问HelloController中的testParam3方法,如果传递给服务器的日期数据是如下格式:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-5YF7btLh-1611577268280)(第二阶段讲义04.assets/image-20200410165410638.png)]

从图中可以看出,如果日期参数是 yyyy-MM-dd格式(以横杠分隔)就会出现400错误,其实是因为参数格式匹配错误,由于Springmvc默认的日期格式是yyyy/MM/dd(以斜杠分隔),因此如果日期参数不是yyyy/MM/dd 格式,就会出现400错误!!

2、解决方案:

在Springmvc中,提供了@InitBinder注解,用于指定自定义的日期转换格式,因此,我们只需要在Controller类中添加下面的代码即可,在接受日期类型的参数时,会自动按照自定义的日期格式进行转换。

 /* 自定义日期格式转换器* 将Springmvc默认以斜杠(/)分隔日期改为以横杠分隔(-)*/@InitBinderpublic void InitBinder (ServletRequestDataBinder binder){binder.registerCustomEditor(java.util.Date.class, new CustomDateEditor(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"), true));}

3、再次测试:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-DWkDdq8b-1611577268282)(第二阶段讲义04.assets/image-20200411125752937.png)]

控制台输出结果为:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ycNxPmTI-1611577268284)(第二阶段讲义04.assets/image-20200411125827915.png)]

跳转和乱码处理

实现转发(forward)

在前面request对象的学习中,通过request对象可以实现请求转发(即资源的跳转)。同样的,Springmvc也提供了请求转发的方式,具体实现如下:

需求:通过浏览器访问 testForward方法,执行testForward方法后,将请求转发到(HelloController)hello, 也就是home.jsp页面。

1、在HelloController中,提供testForward方法,代码实现如下:

/* 测试请求转发(forward) */
@RequestMapping("testForward")
public String testForward(){System.out.println("测试请求转发(forward)...");return "forward:hello";
}

2、打开浏览器,在浏览器中输入:http://localhost/day16-Springmvc/testForward地址,访问效果如下:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-6bfwZkrb-1611577268286)(第二阶段讲义04.assets/image-20200411125952494.png)]

forward方式相当于:

request.getRequestDispatcher("url").forward(request,response);

转发是一次请求,一次响应;

转发后地址栏地址没有发生变化(还是访问testForward的地址);

转发前后的request和response对象也是同一个。

实现重定向(redirect)

在前面response对象的学习中,通过response对象可以实现请求重定向(即资源的跳转)。

同样的,Springmvc也提供了请求重定向的方式,具体实现如下:

需求:通过浏览器访问 testRedirect方法,执行testRedirect方法后,将请求重定向到
(HelloController)hello, 也就是home.jsp页面。

1、在HelloController中,提供testRedirect方法,代码实现如下:

/* 测试请求重定向(redirect) */
@RequestMapping("testRedirect")
public String testRedirect(){System.out.println("测试请求重定向(redirect)...");return "redirect:hello";
}

2、打开浏览器,在浏览器中输入:http://localhost/day16-Springmvc/testRedirect地址,访问效果如下:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-F7XikUfC-1611577268289)(第二阶段讲义04.assets/image-20200411130114886.png)]

redirect方式相当于:

response.sendRedirect(url);

重定向是两次请求,两次响应;

重定向后地址栏地址发生了变化(变为转发后的地址);

并且在重定向前后,request和response对象不是同一个。

乱码处理

在前面的Servlet学习中,我们学习了GET和POST请求参数乱码如何解决。

Springmvc也提供了解决请求参数乱码的方案,就是在web.xml中加入如下代码(配置请求参数乱码过滤器),可以解决POST提交的中文参数乱码问题!

<!-- 配置过滤器,解决POST提交的中文参数乱码问题 -->
<filter><filter-name>encodingFilter</filter-name><filter-class>org.Springframework.web.filter.CharacterEncodingFilter</filter-class><init-param><param-name>encoding</param-name><param-value>UTF8</param-value></init-param>
</filter>
<filter-mapping><filter-name>encodingFilter</filter-name><url-pattern>/*</url-pattern>
</filter-mapping>

Servlet中,两种请求方式乱码解决方案回顾:

(1)如果请求方式为POST提交,必然会出现乱码,解决方式是在任何获取参数的代码之前,添加如下代码:

request.setCharacterEncoding("utf-8");

(2)如果请求方式为GET提交,tomcat8及之后的版本已经解决了GET提交的中文参数乱码问题,因此不需要处理;在 tomcat7 及之前的版本中,获取GET提交的中文参数仍有乱码,解决方法是:只需要在[tomcat]/conf/server.xml中添加如下配置也可以解决乱码问题。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-93fbRocb-1611577268291)(第二阶段讲义04.assets/31a1d635302b07dcec001a8cb8a27a4f.png)]

Springmvc响应数据

Model的使用

当请求发起访问Controller中的方法时,可以通过参数声明,在方法内使用Model。

@RequestMapping("/doorList")
public String doorList(Model model){}

Model对象实际上是一个Map集合,例如:往model中添加一个属性

model.addAttribute(String name, Object value);

其中,addAttribute方法会将属性保存到request域中,再通过转发将属性数据带到相应的JSP中,通过${}取出并显示。

示例,往Model中添加属性:

@RequestMapping("/testModel")
public String testModel(Model model){/* 往Model添加属性 */model.addAttribute("name", "马云");model.addAttribute("age", 20);return "home";
}

在home.jsp中取出属性并显示:

<body><h1>hello Springmvc~~~</h1>${ name } <br/>${ age }
</body>

扩展内容

Springmvc前端控制器放行静态资源的解决办法

在配置SpringMVC开发环境时,会在web.xml文件中配置SpringMVC的前端控制器,将所有请求交给前端控制器处理,因此在url-pattern中配置了斜杠(/):

<!-- 1.配置Springmvc前端控制器, 并将所有请求交给Springmvc处理 -->
<servlet><servlet-name>Springmvc</servlet-name><servlet-class>org.Springframework.web.servlet.DispatcherServlet</servlet-class><!-- 配置Springmvc核心配置文件的位置,默认Springmvc的配置文件是在WEB-INF目录下,默认的名字为Springmvc-servlet.xml,如果要放在其他目录,则需要指定如下配置:--><init-param><param-name>contextConfigLocation</param-name><param-value>classpath:Springmvc-config.xml</param-value></init-param></servlet>
<servlet-mapping><servlet-name>Springmvc</servlet-name><!-- 斜杠表示拦截所有请求(除JSP以外) --><url-pattern>/</url-pattern>
</servlet-mapping>

url-pattern中配置的斜杠(/)表示将除了JSP以外的其他请求都拦截下来,交给Spring的前端控制器来处理。

但是这样配置,会将对静态资源的访问也拦截下来,导致访问静态资源时,出现404(资源找不到),因为Spring的前端控制器将对静态资源的访问也当成了一个controller请求,去配置对应的映射路径,这当然找不到。

比如访问:http://localhost/day15-Springmvc/home.html,由于配置的是斜杠(/),所以此时会拦截静态资源,到controller中匹配路径为/home.html的方法,此时自然是匹配不到的。

如果需要访问到静态资源,让前端控制器对静态资源的请求放行。此时可以在Springmvc-config.xml文件中添加放行静态资源的配置:

<!-- 1.配置前端控制器放行静态资源(html/css/js等,否则静态资源将无法访问) -->
<mvc:default-servlet-handler/>

beans.xml没有提示的解决方法

1、配置Spring-beans-4.1.xsd文件

(1)找到Spring-beans-4.1.xsd的文件的位置,例如:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-KkwFtqCA-1611577268293)(第二阶段讲义04.assets/3526b8f7cb18db7def8c59c6b8f5a8c6.png)]

(2)复制下面的url地址:

http://www.Springframework.org/schema/beans/Spring-beans-4.0.xsd

(3)在eclipse菜单栏中: window --> Preferences --> 在搜索框中搜索 [ xml ]

XML --> XML Catalog --> User Specified Entries --> Add…

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-NwyPkpRD-1611577268296)(第二阶段讲义04.assets/eef68594c0d7b072e09702b3d6289831.png)]

(4)在弹出的窗口中:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-dSGrV0lv-1611577268298)(第二阶段讲义04.assets/10a6f5e3901af4d2fd9cba0d644e00bd.png)]

2、配置Spring-context-4.0.xsd文件

(1)找到Spring-context-4.0.xsd的文件的位置,例如:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-2HK1F213-1611577268300)(第二阶段讲义04.assets/f88878c81816bfccdaf534b48ecbf37d.png)]

(2)复制下面的url地址:

http://www.Springframework.org/schema/context/Spring-context-4.0.xsd

(3)在eclipse菜单栏中: window --> Preferences --> 在搜索框中搜索 [ xml ]

XML --> XML Catalog --> User Specified Entries --> Add…

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-RZ1GBVoF-1611577268303)(第二阶段讲义04.assets/eef68594c0d7b072e09702b3d6289831.png)]

(4)在弹出的窗口中:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-KCcXQM0c-1611577268308)(第二阶段讲义04.assets/bab1eb1bf264e64de4a3672c96f23e8b.png)]

er-mapping>
encodingFilter
/*


Servlet中,两种请求方式乱码解决方案回顾:(1)如果请求方式为**POST**提交,必然会出现乱码,解决方式是在任何获取参数的代码之前,添加如下代码:```java
request.setCharacterEncoding("utf-8");

(2)如果请求方式为GET提交,tomcat8及之后的版本已经解决了GET提交的中文参数乱码问题,因此不需要处理;在 tomcat7 及之前的版本中,获取GET提交的中文参数仍有乱码,解决方法是:只需要在[tomcat]/conf/server.xml中添加如下配置也可以解决乱码问题。

[外链图片转存中…(img-93fbRocb-1611577268291)]

Springmvc响应数据

Model的使用

当请求发起访问Controller中的方法时,可以通过参数声明,在方法内使用Model。

@RequestMapping("/doorList")
public String doorList(Model model){}

Model对象实际上是一个Map集合,例如:往model中添加一个属性

model.addAttribute(String name, Object value);

其中,addAttribute方法会将属性保存到request域中,再通过转发将属性数据带到相应的JSP中,通过${}取出并显示。

示例,往Model中添加属性:

@RequestMapping("/testModel")
public String testModel(Model model){/* 往Model添加属性 */model.addAttribute("name", "马云");model.addAttribute("age", 20);return "home";
}

在home.jsp中取出属性并显示:

<body><h1>hello Springmvc~~~</h1>${ name } <br/>${ age }
</body>

扩展内容

Springmvc前端控制器放行静态资源的解决办法

在配置SpringMVC开发环境时,会在web.xml文件中配置SpringMVC的前端控制器,将所有请求交给前端控制器处理,因此在url-pattern中配置了斜杠(/):

<!-- 1.配置Springmvc前端控制器, 并将所有请求交给Springmvc处理 -->
<servlet><servlet-name>Springmvc</servlet-name><servlet-class>org.Springframework.web.servlet.DispatcherServlet</servlet-class><!-- 配置Springmvc核心配置文件的位置,默认Springmvc的配置文件是在WEB-INF目录下,默认的名字为Springmvc-servlet.xml,如果要放在其他目录,则需要指定如下配置:--><init-param><param-name>contextConfigLocation</param-name><param-value>classpath:Springmvc-config.xml</param-value></init-param></servlet>
<servlet-mapping><servlet-name>Springmvc</servlet-name><!-- 斜杠表示拦截所有请求(除JSP以外) --><url-pattern>/</url-pattern>
</servlet-mapping>

url-pattern中配置的斜杠(/)表示将除了JSP以外的其他请求都拦截下来,交给Spring的前端控制器来处理。

但是这样配置,会将对静态资源的访问也拦截下来,导致访问静态资源时,出现404(资源找不到),因为Spring的前端控制器将对静态资源的访问也当成了一个controller请求,去配置对应的映射路径,这当然找不到。

比如访问:http://localhost/day15-Springmvc/home.html,由于配置的是斜杠(/),所以此时会拦截静态资源,到controller中匹配路径为/home.html的方法,此时自然是匹配不到的。

如果需要访问到静态资源,让前端控制器对静态资源的请求放行。此时可以在Springmvc-config.xml文件中添加放行静态资源的配置:

<!-- 1.配置前端控制器放行静态资源(html/css/js等,否则静态资源将无法访问) -->
<mvc:default-servlet-handler/>

beans.xml没有提示的解决方法

1、配置Spring-beans-4.1.xsd文件

(1)找到Spring-beans-4.1.xsd的文件的位置,例如:

[外链图片转存中…(img-KkwFtqCA-1611577268293)]

(2)复制下面的url地址:

http://www.Springframework.org/schema/beans/Spring-beans-4.0.xsd

(3)在eclipse菜单栏中: window --> Preferences --> 在搜索框中搜索 [ xml ]

XML --> XML Catalog --> User Specified Entries --> Add…

[外链图片转存中…(img-NwyPkpRD-1611577268296)]

(4)在弹出的窗口中:

[外链图片转存中…(img-dSGrV0lv-1611577268298)]

2、配置Spring-context-4.0.xsd文件

(1)找到Spring-context-4.0.xsd的文件的位置,例如:

[外链图片转存中…(img-2HK1F213-1611577268300)]

(2)复制下面的url地址:

http://www.Springframework.org/schema/context/Spring-context-4.0.xsd

(3)在eclipse菜单栏中: window --> Preferences --> 在搜索框中搜索 [ xml ]

XML --> XML Catalog --> User Specified Entries --> Add…

[外链图片转存中…(img-RZ1GBVoF-1611577268303)]

(4)在弹出的窗口中:

[外链图片转存中…(img-KCcXQM0c-1611577268308)]

mybatis spring springMVC相关推荐

  1. 【BackEnd--SSM 框架详解】Mybatis+Spring+SpringMVC学习笔记(完整详细版)

    BackEnd 学习笔记 1 Java 2 SSM框架(Mybatis+Spring+SpringMVC) 3 SpringBoot(SpringBoot 3.x+MybatisPlus) 4 Spr ...

  2. mybatis+spring+springmvc ssm整合

    文章目录 mybatis 开发我的第一个mybatis程序 关于mybatis的核心API:SqlSession对象. mybatis连接数据库操作 log4j jackson parameterTy ...

  3. myeclipes10.7+maven+myBatis+spring+springMvc

    SSM框架详细整合教程(Spring+SpringMVC+MyBatis) 2016年02月26日 01:50:21 程序员囧辉 阅读数:60951 版权声明:本文为博主原创文章,未经博主允许不得转载 ...

  4. Maven+Mybatis+Spring+SpringMVC实现分页

    一.项目搭建 Spirng+SpringMVC+Maven+Mybatis+MySQL项目搭建 二.分页插件的介绍 博主采用的插件是PageHelper这个插件,使用起来十分方便.该插件支持以下数据库 ...

  5. (Spring+SpringMVC+MyBatis)SSM三大框架整合教程

    目录 一.基本概念 1.Spring 2.SpringMVC 3.MyBatis 二.开发环境搭建 三.Maven Web项目创建 四.SSM整合 1.Maven引入需要的JAR包 2.Spring与 ...

  6. SSM框架整合(Spring+SpringMVC+MyBatis)

    输出结果 1.Maven Web项目创建 之前有写过Eclipse+Maven创建web项目的帖子,如果需要,请参考这里写链接内容 创建好项目之后因为入下图: 2.SSM整合 2.1 引入需要的JAR ...

  7. Java项目:在线水果商城系统(java+JSP+Spring+SpringMVC +MyBatis+html+mysql)

    源码获取:博客首页 "资源" 里下载! 一.项目简述 功能: 区分为管理员用户和普通用户,普通用户:用户注册登录,首页水果展示,商品分类展示,购物车添加,下单,订单查询,个人信息修 ...

  8. springMvc+mybatis+spring 整合 包涵整合activiti 基于maven

    2019独角兽企业重金招聘Python工程师标准>>> 最近自己独立弄一个activiti项目,写一下整合过程: 环境:jdk1.7 tomcat7.0 maven3.5  ecli ...

  9. Spring+SpringMVC+Mybatis整合

    一.简单测试工程搭建 1.Mybatis所需要的的jar包(包含数据库驱动包和相关的日志包).SpringMVC和Spring的jar包 2.然后构建一个基本的工程,这里我们使用mapper代理的方式 ...

最新文章

  1. 我为什么晚上写代码?
  2. [征询意见]准备采用“创作共用”协议保护大家的原创作品
  3. android 音乐播放器中播放模式的设计
  4. CentOS7安装mysql数据库
  5. Unity中使用Attribute
  6. python表单提交的两种方式_Flask框架学习笔记之表单基础介绍与表单提交方式
  7. 上海民警这个方法让汽车提前“助跑”,路口通行效率提10%
  8. ubuntu中wps缺失字体
  9. oracle函数大全-字符处理函
  10. 分享Visual SVN Hook Script—— 允许客户端编辑SVN log message
  11. 详述2022诺贝尔物理学奖:量子纠缠实验史
  12. 新建计算机管理员账户,Win10如何新建本地管理员账户
  13. 愚人节主题的微信公众号图文排版有哪些搞怪素材?
  14. django 框架 SQL 语句 查询篇
  15. 基于JAVA健康生活网站计算机毕业设计源码+系统+mysql数据库+lw文档+部署
  16. 别再吐槽12306了!有本事你来写架构
  17. SPM AC原点校正
  18. 【neutron源码分析】neutron-server启动流程分析
  19. 一文了解程序员必须要知道的JVM和性能优化知识点
  20. html制作电影界面,电影网站界面设计HTML_CSS模板

热门文章

  1. 计算机编码与解码编码表
  2. php自动收录导航程序,最新自动收录自带查反链导航源码
  3. linux命令行和shell脚本编程大全笔记
  4. Flink实践:跨境电商 Shopee 的实时数仓之路
  5. 证书透明度Certificate Transparency
  6. 敏捷生产力:意志力和神经科学方法
  7. 一年中重要的节日列表_一年中最重要的节日分别是?
  8. git配置(SSH)
  9. MT4电脑版下载和安装
  10. camera基础知识(1)