JAVAWEB-NOTE04:MyBatis、Spring、SpringMvc

1. unit12-mybatis框架

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-oHYVv8ah-1638456215504)(JAVAWEB-NOTE04.assets/a0a2eb9fa872009998f7cd818925095d.png)]

课程计划:

1、MyBatis快速入门

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

3、#{}占位符的应用

4、动态SQL的应用

5、MyBatis的Mapper接口开发

1.1 MyBatis简介(了解)


1.1.1 什么是MyBatis

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

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

持久层:连接并访问数据库的一层叫持久层,如 jdbc MyBatis

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

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

下图是MyBatis架构图:参考 mybatis数据库的封装图 day14 15

有set方法优先使用set方法封装信息.

在这里插入图片描述

在这里插入图片描述

(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查询的结果映射成相应的结果)

1.1.2 为什么要使用MyBatis

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

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

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

(2)JDBC自身没有连接池,会频繁的创建连接和关闭连接,效率低;(但是可以使用第三方的连接池如: c3p0)

(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框架中几乎都得到了解决!!

1.2 MyBatis快速入门


1.2.1 准备数据,创建库和表

创建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);

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

1、创建Maven的java工程

在这里插入图片描述

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

在pom.xml文件中引入相关依赖包即可 在pom文件里空白处右键 Maven—Add Dependency(添加依赖) dependency :依赖

ctrl+A+i 自动对齐.

<dependencies>
    <!-- junit单元测试 -->
    <dependency>
        <groupId>junit</groupId>
        <artifactId>junit</artifactId>
        <version>4.9</version> //4.9有可能下不下来,用4.0
    </dependency>
    <!-- mysql驱动 ,连接mysql必定需要的jar包-->
    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
        <version>5.1.32</version>
    </dependency>
    <!-- mybatis的jar包 -->
    <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测试类(class类),并提供findAll方法(查询emp表中所有的员工信息),开发步骤如下:

在这里插入图片描述

/** 练习1(快速入门): 查询emp表中的所有员工, 返回一个List<Emp>集合
* @throws IOException */
package com.tedu.mybatis;
import java.io.IOException;
import java.io.InputStream;
import java.util.List;
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 org.junit.Test;
import com.tedu.pojo.Emp;
/**
 * mybatis的入门案例: 查询yonghedb.emp表中的所有员工信息
 */
public class TestMybatis01 {
	
	/* 练习1: 查询emp中的所有员工信息 */
	@Test    单元测试
	public void testFindAll01() throws Exception {
		//1.读取mybatis核心配置文件(mybatis-config.xml),Resources是mybatis提供的工具类   
		InputStream in = Resources.getResourceAsStream("mybatis-config.xml");
		//2.通过配置信息获取SqlSession工厂对象
		SqlSessionFactory fac = new SqlSessionFactoryBuilder().build( in );
		//3.获取SQLSession对象(打开与数据库的连接)   这个session跟会话的session没有关系
		SqlSession session = fac.openSession();
		//4.执行SQL语句,返回处理后的结果(EmpMapper.xml),参数是SQL语句的定位标识除了查询,增删该可以混用 但不推荐        查询一条用 selectone 多条用selectList   .是分隔符不能省 (左边是文件名  右边是id值来定位这个执行的sql语句是哪个)
		List<Emp> list = session.selectList( "EmpMapper.findAll01" );
		//5.输出结果
		for (Emp emp : list) {
			System.out.println( emp ); //打印是地址值? 是因为没有重写toString方法
		}
	}
}

mybatis的增删改操作:

运行测试方法 右键—run as–JUnit Test

package com.tedu.mybatis;

import java.io.InputStream;
import java.util.HashMap;
import java.util.Map;

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 org.junit.Before;
import org.junit.Test;

import com.tedu.pojo.Emp;

/**
 * 实现mybatis的增删改操作
 * 使用mybatis的#{}占位符
 * 使用mybatis的${}占位符
 */
public class TestMybatis02 {
	private SqlSession session = null;
	
	/* @Before注解标记的方法,会在每个@Test注解标记的方法执行之前执行
       @After单元测试  之后执行  这2个方法不用选中执行,只需要执行测试方法会在他之前之后自动执行  */
	@Before
	public void testBefore() throws Exception {
		//1.读取mybatis核心配置文件(mybatis-config.xml),Resources是mybatis提供的工具类,//这个配置文件放在源码目录下,将来经过编译会放在类目录下,这个方法就是读目录下的文件,所以我们只需要读这个文件名就行.在 文件上ctr c  就可以直接复制这个文件名   ctrl+v复制到需要的地方.
		InputStream in = Resources.getResourceAsStream("mybatis-config.xml");
		//2.通过配置信息获取SqlSession工厂对象   
		SqlSessionFactory fac = new SqlSessionFactoryBuilder().build( in );
		//3.获取SqlSession对象(打开与数据库的连接),参数true表示设置自动提交事务,默认值是false,表示手动提交   这个session跟会话的session没有关系
		session = fac.openSession( true );  //在类上声明整个类上都能使用
	}
	/* 练习2: 新增员工信息: 赵云 保安 6000 
	 * 注意:mybatis将jdbc的自动提交事务关闭了,因此现在默认会开启事务,但是由于最后没有提交事务
	 * 因此即使数据插入操作执行成功了,但由于没有提交,所以数据会回滚,并没有插入到数据库中。
	    注意:id值不会回滚
	    增,删,改,返回值都是影响的行数.
	 */
	@Test
	public void testInsert02() {
		//执行SQL语句,返回执行结果
		int rows = session.insert( "EmpMapper.insert02" );
		System.out.println( "影响行数: "+rows );
		//提交事务,每次手动提交事务太麻烦,可以设置mybatis自动提交事务!(在上面步骤3)
		//session.commit(); 手动提交
	}
	/* 练习3: 修改员工信息:赵云 保镖 20000 */
	@Test
	public void testUpdate03() { 
		int rows = session.update( "EmpMapper.update03" );
		System.out.println( "影响行数: "+rows );
	}
	/* 练习4: 删除name为'赵云'的记录 */
	@Test
	public void testDelete04() {
		int rows = session.delete( "EmpMapper.delete04" );
		System.out.println( "影响行数: "+rows );
	}
	
	//=================== mybatis占位符 =================== 
	/* 练习5: 查询emp表中指定id的员工信息   一个占位符传参 */
	@Test
	public void testFindById05() {
		Integer id = 1; 
		//sql语句有个占位符,就需要传一个参数.不然sql语句不完整.
		//表示查询id为3的员工信息
		Emp emp = session.selectOne( "EmpMapper.findById05", id );
		System.out.println( emp );   
	}
	
	/* 练习6: 新增员工信息: 张飞 Java开发工程师 15000  多个占位符传参 
	增,删,改,插4个方法传参,最多只能传2个.一个是names+id占用,另一个只能被一个参数占用.
	解决:把参数封装到map集合中或者pojo对象
	*/
	@Test
	public void testInsert06() {
		//传多个参数,将SQL语句中的参数封装到map集合中,再将map集合传递过去  key要对应占位符里面的名称,用来区分传的参数是对应的占位符保持一致.
		//Map map = new HashMap();
		//map.put( "name" , "张飞" );
		//map.put( "job" , "Java开发工程师" );
		//map.put( "salary" , 15000 );  
       // session.insert( "EmpMapper.insert06", map );
       
		//也可以可以将SQL语句中的参数值封装到pojo对象中   3oooo是个整数可以化为double类型但不能转化为double类型的包装类型,加个d变为小数  更多的是用这种方式 因为将来这写数据都是从前端页面传给服务器,接收这些从前端传到服务器的参数是用到的都是springmvc框架,他里面有参数绑定机制,可以非常简单的封装到pojo对象中,所以更多的是用这种方式。(这个地方赋值可以直接调用构造方法,也可以写一个有参构造,但要注意写一个无参的构造方法)
		Emp  emp =new  Emp(null, "刘备",“CEO”,30000d);
		session.insert( "EmpMapper.insert06", emp );
		
	}
	/* 练习7: 修改员工信息: 张飞 架构师 25000 */
	@Test
	public void testUpdate07() {
		//将SQL语句中的参数封装到map集合中,再将map集合传递过去
		//Map map = new HashMap();
		//map.put( "name" , "张飞" );
		//map.put( "job" , "架构师" );
		//map.put( "salary" , 25000 );
		//session.update( "EmpMapper.update07" , map );
		Emp  emp =new  Emp(null, "张飞",“董事长”,35000d);
		session.update( "EmpMapper.insert07", emp );
	}
	/* 练习8(自己完成):删除emp表中指定id的员工信息 */
	@Test
	public void testDelete08() {}
}

	/* #{}占位符: 相当于JDBC中的问号(?)占位符,它是为SQL语句中的参数值(插入的值 或 列=值 中的值都是参数值)
	 * 	进行占位, 大多数情况下还是使用 #{} 占位符, 可以防止SQL注入攻击!
	 * 	#{}占位符如果只有一个,可以直接将参数传给SQL语句,不用封装到POJO对象或者map集合
	 * ${}占位符: 它为SQL片段(SQL语句中的某一段)进行占位,将来将参数传过来,是直接代替 ${}占位符,拼接
	 * 	在SQL语句, 因为是直接拼接, 不会做任何处理, 因此可能会产生SQL注入攻击!
	 * 	 ${}占位符即使只有一个,也需要先将参数值封装到POJO对象或map集合,再传递给SQL语句 因为:原因在练习9中
	 *   /
/* 练习9: 动态指定要显示的列 -- 只能使用${}占位符
	 * []编号(id) [√]姓名 [√]职业 [√]薪水   显示的列一直在变化.
	 * select id,name from emp;
	 * select id,name,job from emp;
	 * select id,name,job,salary from emp;
	 * select name,job from emp;
	 * ... */
	@Test
	public void testFindAll09() { 
		Map map = new HashMap();
		map.put( "cols", "name,job" );  //查询的结果是一个list集合
		//List<Emp> list = session.selectList( "EmpMapper.findAll09", "id,name" );
		//  直接传它会认为你传的是一个对象,它会找getcols方法(根据你写的占位符的名称cols会拼接位getcols方法),但是字符串上根本就没有这个方法所以会报错。
		List<Emp> list = session.selectList( "EmpMapper.findAll09", map );
		for (Emp emp : list) {
			System.out.println( emp );
		}
	}
	
	/* 练习10: 根据name模糊查询emp表 -- 可以使用#{},也可以使用${} */
	@Test
	public void testFindByName10() { 
		List<Emp> list = session.selectList( "EmpMapper.findByName10", "%刘%" );	
		for (Emp emp : list) {
			System.out.println( emp );
		}
	}
	/* 练习11: 根据name模糊查询emp表 -- 可以使用#{},也可以使用${} 
	${}占位符哪怕只需要传一个参数也要先把它传入map集合中进行封装 */
	@Test
	public void testFindByName11() { 
		Map map = new HashMap();
		map.put( "name",  "刘" ); //两边已经有%号了,这个地方可以传%也可以不传,因为即便传了也代表一个或多个字符,多加几个效果一样.
		List<Emp> list = session.selectList( "EmpMapper.findByName11", map );	
		for (Emp emp : list) {
			System.out.println( emp );
		}
	}
	
	/* 练习12: 根据薪资查询员工信息
	 * select * from emp -- 最低薪资和最高薪资都没传
	 * select * from emp where salary >= #{minSal} -- 只传了最低薪资
	 * select * from emp where salary <= #{maxSal} -- 只传了最高薪资
	 * select * from emp where salary>= #{minSal} and salary<=#{maxSal}
	 * 	-- 既传了最低薪资,也传了最高薪资
	优缺点:如这里,如果没有动态标签,你需要写4条sql语句.然后再程序里你还需要对参数进行判断.
	但是用动态sql标签只需要写一个就行了
	 */
	@Test
	public void testFindBySal12() {
		Map map = new HashMap();
		map.put("minSal", 3000); //传最低薪资
		map.put("maxSal", 5000); //传最高薪资
		List<Emp> list = session.selectList( "EmpMapper.findBySal13", map );
		for (Emp emp : list) {
			System.out.println( emp );
		}
	}
	
	/* 练习14: 根据员工的id批量删除员工信息 */
	@Test
	public void testDeleteByIds14() {
		Integer[] ids = {1,3,5,7};
		session.delete("EmpMapper.deleteByIds14", ids);
	}
	
	/* 练习15: 根据员工的id批量更新员工信息
 	将id为 2、4、6、8的员工的薪资在原有基础上增加1000 */
	@Test
	public void testUpdateByIds15() {
		Integer[] ids = {2,4,6,8}; //要被涨薪资的员工的id值
		Double money = 1000.0; //要涨的薪资数
		Map map = new HashMap(); //因为要存2个参数所以用map集合进行封装
		map.put( "ids" , ids );
		map.put( "money" ,  money );
		session.update( "EmpMapper.updateByIds15", map );
	}
	
	
}

1.2.3 添加mybatis-config.xml文件

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

在这个目录下 右键—new----other—搜索 xml file 选中 XML File—next 写文件名字

在这里插入图片描述

2、mybatis-config.xml文件配置如下: 可以点source进入编辑模式

mybatis-config文件头信息如下: 注意这里面为了方便添加了错误的格式注释.

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE configuration
    PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
    "http://mybatis.org/dtd/mybatis-3-config.dtd">
  
    
<!-- MyBatis的全局配置文件 -->
<configuration >
	 在这个标签下  alt+/  如果有网的话会有提示,但是也有有网没出现提示的情况。
    参考:扩展内容
</configuration>

mybatis-config文件详细配置如下: 这些标签不需要记,只需要记他的 该用到什么就行了 springboot可以实现0配置

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE configuration
    PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
    "http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
	<!-- 1.配置开发环境: 
		id属性是为当前环境指定一个名称,default属性指定使用哪一个环境  可能有多个环境 -->
	<environments default="test">
		<environment id="test">
			<!-- 1.1.配置事务管理方式  type="JDBC|MANAGED" 
				JDBC:将事务交给JDBC管理   MANAGED:自己管理事务(不推荐) -->
			<transactionManager type="JDBC" />
			<!-- 1.2.配置数据源(连接池) type:指定连接池方式, 
				POOLED:使用连接池 可以提高程序的效率, UNPOOLED表示不使用连接池, JNDI:不常用 -->
			<dataSource type="POOLED">  //这是个name值是固定的不能改,右面的看你连接到那个数据库
				<property name="driver" value="com.mysql.jdbc.Driver"/> //driver:驱动程序
				<property name="url" value="jdbc:mysql:///yonghedb?     characterEncoding=utf-8"/>  // url:网址  
				<property name="username" value="root"/>
				<property name="password" value="root"/>
			</dataSource>
		</environment>
	</environments>
	
	<!-- 2.导入XxxMapper.xml文件,将来读这个文件顺带回家再maper文件
		resource属性指向的是类目录,mapper文件存放在源码目录下,当编译后会
		输出到类目录下   直接ctr c  v在文件上可以直接把文件名复制过来 -->
	<mappers>
		<mapper resource="EmpMapper.xml"/>    多个在写一个mapper标签
	</mappers>

</configuration>

1.2.4 添加EmpMapper.xml文件

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

在这里插入图片描述

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

EmpMapper文件头信息如下:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper
	PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
<!--这是一个说明文档,规定xml文件需要的规范-->
	"http://mybatis.org/dtd/mybatis-3-mapper.dtd">

<!-- 
	不同Mapper文件的namespace值应该保证唯一
	在程序中通过[ namespace + id ]定位到要执行哪一条SQL语句
 -->
<mapper namespace="">
	
	
</mapper>

EmpMapper文件详细配置如下: 添加sql语句

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper
	PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
<!--说明文档,规定xml文件的书写规范-->
	"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="EmpMapper">
	<!-- 练习1: 查询emp中的所有员工信息
		select标签用于编写查询类型的SQL语句,insert/delete/update标签用于编写增删改类型的SQL语句
		id属性是每个标签必须存在的属性,用于指定一个独一无二的编号,将来和namespace配合来定位要执行的SQL语句
			因此每个SQL标签的id值都不能重复!
		resultType属性只能用在select标签上,用于指定使用什么类型,来封装查询后的结果(这个类型我们来指定,就是一个类封装信息)
			例如:使用Emp类来封装员工信息, resultType就指向Emp类的全限定类名(包名+类名)  即员工类封装员工信息
			List<Emp>:他返回的是一个集合为什么不用指定集合呢,如果返回的是一个对象可以用集合,如果返回的是集合只需要指定集合中的泛型就行  (因为返回的对象不止一个所以封装到集合中,即使返回的最终结果是集合,resultType也只需要指定集合中的泛型即可)
        如:返回的是一个对象:resultType="List<com.tedu.pojo.Emp>" 
选中类名右键--Copy  Qualified  Name复制类的全称限定类目,复制完后 ctrl 选中这个限定类目 鼠标点击出现下划线说明配置成功
   -->
	<select id="findAll01" resultType="com.tedu.pojo.Emp">
		select * from emp
	</select>
	
	<!-- 练习2: 新增员工信息: 赵云 保安 6000 id设置了自增不用给值设置为null-->
	<insert id="insert02">
		insert into emp value(null,'赵云','保安',6000)
	</insert>
	
	<!-- 练习3: 修改员工信息:赵云 保镖 20000 -->
	<update id="update03">
		update emp set job='保镖',salary=20000
		where name='赵云'
	</update>
	<!-- 练习4: 删除name为'赵云'的记录 -->
	<delete id="delete04">
		delete from emp where name='赵云'
	</delete>
    
    //这是不写死的增删改查
    <!--mybatis中的占位符相当于jdbc的?占位符 #{}输出到控制台会变成 ?占位符  括号里面必须写一个名称,不能为空-->
	<!-- 练习5: 查询emp表中指定id的员工信息 -->
	<select id="findById05" resultType="com.tedu.pojo.Emp">
		select * from emp where id=#{id}
	</select>
	<!-- 练习6: 新增员工信息: 张飞 Java开发工程师 15000
      如果使用map封装SQL语句中的参数,要保证#{}占位符中的名称和map集合中的key保持一致,因为在将map集合传入过来后,mybaties框架底层会根据#{}占位符中的名称作为key,到map中取出相应的value,如果没有对应的key,值最终为null -->
    //如果使用EMP对象(pojo)封装SQL语句中的参数,(1)要保证#{}占位符中的名称 在Emp对象中有对应的getXxx方法;(2)或者#{}占位符中的名称在Emp对象中相同名称的变量  通过私有属性取值但是没有通过get方法取值效率高   如果都有会优先显示有get方法,私有的底层还需要设置权限效率变低了。(框架底层已经设置好权限了,我们只需要拿过来用)
    
    <!--id属性设置了自增,所以这里写null值-->
	<insert id="insert06">
		insert into emp value(null, #{name}, #{job}, #{salary})
	</insert>
	<!-- 练习7: 修改员工信息: 张飞 架构师 25000 -->
	<update id="update07">
		update emp set job=#{job}, salary=#{salary}
		where name=#{name}
	</update>	
<!-- 练习9: 动态指定要显示的列   getCols() -->
	<select id="findAll09" resultType="com.tedu.pojo.Emp">
		select ${cols} from emp
	</select>
	<!-- 练习10: 根据name模糊查询emp表 ~使用#{}占位符  %模糊词% 
     xml注释不能写(--) 会报错,他对注释要求特别严格   -->
	<select id="findByName10" resultType="com.tedu.pojo.Emp">
		select * from emp where name like #{name}
	</select>
	<!-- 练习11: 根据name模糊查询emp表 ~使用${}占位符  模糊词 刘 -->
	<select id="findByName11" resultType="com.tedu.pojo.Emp">
		select * from emp where name like '%${name}%'
	</select>
	<!-- 练习12: 根据薪资查询员工信息 (&lt;表示小于号,&gt;表示大于号)   ( 使用if标签)
      为什么用转义字符:因为xml文件引擎在解析时会把它当做 标签的开始符号<而不是小于号< ,所以我们用转义字符来代替   大于号其实不用转义
     注意:resultType标签只能用于select标签上(大多数用),updadte,delete,insert不能用
    test用于指定一个boolean值-->
	<select id="findBySal12" resultType="com.tedu.pojo.Emp">
		select * from emp
		where 3=3      //可以加一个等式避免如果用户2个都没传,都为null代码不会执行但是多个where,语法会报错,where也不能删因为万一传了也需要有where, 所以随便写一个想等的式子保证where子句会执行
			<if test="minSal != null">
				and salary >= #{minSal} //中间要加个连接词 and 不同的条件用and连接.查询的范围在最大值最小值之间。
			</if>
			<if test="maxSal != null">
				and salary &lt;= #{maxSal}
			</if>
	</select>
	<!-- 把where3=3换成 where标签
练习13:使用where标签  如果用户传入参数 where子句生效 它会帮你自动生成一个全大写的 WHERE关键字,并且会自动去掉下面多余的and符号,  所以这个地方多写一个and也不会报错.这个地方不用再写入3=3了,因为下面的子句不生效他就不会生成WHERE关键字 -->
	<select id="findBySal13" resultType="com.tedu.pojo.Emp">
		select * from emp
		<where>
			<if test="minSal != null">
				and salary >= #{minSal}
			</if>
			<if test="maxSal != null">
				and salary &lt;= #{maxSal}
			</if>
		</where>
	</select>
	<!-- 练习14: 根据员工的id批量删除员工信息   测试foreach标签
		delete from emp where id in(1,3,5,7)  表示满足其中的一个条件就会执行 
      取这些不确定的内容 可以通过foreach标签 把这些id遍历出来 用","隔开  再用()包起来
      如果传的是数组的话 collection是个固定值: array       写list表示传一个list集合
      open指定起始符号  close指定结束符号  中间是id值不能写死,要用一个占位符写一个名字用来接收id值,数组中的每一个元素.    占位符的名字和item 的名字保持一致   separator:指定分隔符  item:指定的是占位符的名称上下要保持一致.
    以上是传一个参数   2个参数可以用map集合-->
	<delete id="deleteByIds14">
		delete from emp where id in
		<foreach collection="array" open="(" item="id" separator="," close=")">
			#{id}
		</foreach>
	</delete>
	<!-- 练习15: 根据员工的id批量更新员工信息
 	将id为 2、4、6、8的员工的薪资在原有基础上增加1000 -->
	<update id="updateByIds15">
		update emp set salary=salary + #{money} //传2个参数时,需要封装到map集合这里面的值要和map的key保持一致,这样才能根据key找到value的值  传的是一个map集合如何找到里面的数组 ?
       这个数组是先存到map集合中 ,而这个集合中的key代表的value值是ids,就是这个数组的名字,所以collection里只需要存 map中的key,就代表这个数组的名字.
		where id in
		<foreach collection="ids" open="(" item="id" separator="," close=")">
			#{id}
		</foreach> 
	</update>
	
	
</mapper>

1.2.5 添加并编写Emp实体类

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

在src/main/java目录下创建 com.tedu.pojo.Emp类(class),并编辑Emp类:提供私有属性以及对应的getter方法、setter方法,并重写toString方法 把信息封装到 这个类中.比如要新增的信息.企业中更多的使用这种方式,用Map集合进行封装还需要一个一个在方法里添加数据.

package com.tedu.pojo;

/**
 * 用于封装员工信息
 * 在实体类中声明变量,尽量都使用包装类型,可以避免歧义!
 基本类型 默认值为0    引用类型为 nuLl   比如:封装学生的成绩:有的学生考试了,有的学生考试考了0分,有的没去考试。那么怎么区分呢? 考试成绩为0  赋值成绩为0.没有考试不去赋值但他的默认值为0,没法区分,用他的包装类型可以区分,没去考试不赋值为null.  所以尽量使用包装类型。
 private  double  score  默认是0
 private  Double  salary  默认是null
 * Emp类中的属性名尽量和emp表中的列名保持一致(如果不一致,会导致数据封装失败,但可以解决)
 */
public class Emp {
    //有set方法,优先使用set方法给属性赋值,没有set方法可以通过设置私有属性的权限,跟列名相同的私有属性进行赋值
    
	//提供私有属性
	private Integer id;   //私有属性不能赋值   需要设置权限 还不如直接用set方法赋值的效率高
	private String name;
	private String job;
	private Double salary;
    //提供无参构造 
     public Emp() {  //不提供会报错因为mybatis底层会通过反射创建emp对象,不提供会把原先的构造方法给覆盖掉,没法创建对象. 所以保持一个习惯无参要自己提供.
       System.out.println("无参构造执行了");
    }
    //有参构造函数
    public Emp(Integer id,String name,String job,Double salary){
        this.is=id;
        this.name=name;
        this.job=job;
        this.salary=salary;
    }
	//提供对应的get和set方法
	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;
	}
	//重写toString方法
	@Override
	public String toString() {
		return "Emp [id=" + id + ", name=" + name + ", job=" + job + ", salary=" + salary + "]";
	}
}

1.2.6 实现测试类,并测试

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]

1.3 MyBatis入门细节

1.3.1 mybatis-config.xml配置文件

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE configuration
    PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
    "http://mybatis.org/dtd/mybatis-3-config.dtd">
    
<!-- MyBatis的全局配置文件 -->
<configuration >
	<!-- 1.配置环境,可配置多个环境(比如:develop开发、test测试) -->
	<environments default="develop">
		<environment id="develop">
			
			<!-- 1.1.配置事务管理方式:JDBC/MANAGED
			JDBC:将事务交给JDBC管理(推荐)
			MANAGED:自己管理事务
			  -->
			<transactionManager type="JDBC"></transactionManager>
			
			<!-- 1.2.配置数据源,即连接池 JNDI/POOLED/UNPOOLED
				JNDI:已过时
				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文件。

1.3.2 EmpMapper.xml配置文件

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper
	PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
	"http://mybatis.org/dtd/mybatis-3-mapper.dtd">

<!-- 
	不同Mapper文件的namespace值应该保证唯一
	在程序中通过[ 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,但不能同时使用。

1.4 MyBatis增删改查


1.4.1 新增员工

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.4.2 修改员工

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.4.3 删除员工

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);
}

1.5 mybatis中的占位符

1.5.1 #{}占位符

在上面的增删改查操作中,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='王海涛' 

1.5.2 ${}占位符

那么如果我们在传递的时候不是一个参数值,而是一个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 emp
  where name like '%${name}%'
</select>
<!-- 练习11: 根据name模糊查询emp表 -->
<select id="findAll4" resultType="com.tedu.pojo.Emp">
  select * from emp
  where 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片段进行占位!

1.6 动态SQL标签


1.6.1 if、where标签

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

    比如传的是根据薪资范围进行查询的SQL语句.

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

在这里插入图片描述

练习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 emp
  where 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 );
  }
}

1.6.2 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中的key
	open: 指定生成的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的员工的薪资在原有基础上增加1000
 	update emp set salary=salary + 1000
	where 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() {}

1.7 Mapper接口开发


1.7.1 Mapper接口开发介绍

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

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

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

使用mapper接口开发的优点:

1 在写程序时不用再传字符串了(namespace+id),写错也会提示.(其实还是namespace+id只不过换了一种传值的方式)

2.将来和spring容器整合之后由spring容器创建mybatis的对象,代码变得更简单.

(Mapper接口开发 写错会有提示 和spring整合后有spring容器创建对象会变得更简单.)

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

1.7.2 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 mapper
  PUBLIC "-//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 {
	@Test
	public 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 {
  ...
  @Test
  public 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);
  }
}

总测试:

pojo(emp对象省略没有写…)

//  测试类:
package com.tedu.mybatis;

import java.io.InputStream;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

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 org.junit.Before;
import org.junit.Test;

import com.tedu.dao.EmpMapper;
import com.tedu.pojo.Emp;

/**
 * 实现mybatis的mapper接口开发
 */
public class TestMybatis03 {
	private SqlSession session = null;
	
	/* @Before注解标记的方法,会在每个@Test注解标记的方法执行之前执行 */
	@Before
	public void testBefore() throws Exception {
		//1.读取mybatis核心配置文件(mybatis-config.xml),Resources是mybatis提供的工具类
		InputStream in = Resources.getResourceAsStream("mybatis-config.xml");
		//2.通过配置信息获取SqlSession工厂对象
		SqlSessionFactory fac = new SqlSessionFactoryBuilder().build( in );
		//3.获取SqlSession对象(打开与数据库的连接),参数true表示设置自动提交事务,默认值是false,表示手动提交
		session = fac.openSession( true ); 
	}
	
	/* 使用Mapper接口开发需要注意以下四点: 
	 * (1) 提供一个接口,让接口的全限定接口名(包名+接口名) 和 Mapper文件的
	 * 	namespace值保持一致( 接口的全限定接口名 = namespace值  )
	 * (2) 接口中的方法要对应Mapper文件中的SQL语句,方法名 和 SQL语句标签上的
	 * 	id值 要保持一致 ( 接口中的方法名 = SQL标签的id值 )
	 * (3) 接口中的方法的返回值类型,和 SQL标签上的resultType的类型保持一致
	 * 	( 如果方法上返回的是集合,resultType只需要指定集合中的泛型即可! )
	 * (4) 接口中的方法的参数类型, 和SQL标签上的parameterType类型要对应
	 * 	( SQL标签上的parameterType可以省略不写! 但是接口中的方法上的参数不能省,用来接收参数)
	 */
	/* 练习16: 查询所有员工信息 */
	@Test
	public void testFindAll() {
		//List<Emp> list = session.selectList( "EmpMapper.findAll01" );不需要这种形式了
		//获取EmpMapper接口的子类(mybatis提供)实例(mybatis创建)  父接口的接受子类创建的对象
		EmpMapper mapper = session.getMapper( EmpMapper.class );
		//根据接口的子类实例调用 findAll01方法,查询所有员工信息(接口的实现类由框架实现)
		List<Emp> list = mapper.findAll01();
		/* 如何定位到查询所有员工信息的SQL语句的?
		 * 虽然没有显式的传入namespace和SQL标签的id值,只传入了mapper接口的字节码
		 * 对象,以及调用了findAll01方法, 框架底层可以获取 接口的[全限定接口名] 和
		 * 当前[方法的名字], [全限定接口名+方法名]==[namespace+id值]
		 */
		for (Emp emp : list) {
			System.out.println( emp );
		}
	}
	
	/* 练习17: 增员工信息: 张飞 Java开发工程师 15000 */
	@Test
	public void testInsert() {
		//获取EmpMapper接口的子类(mybatis提供)的实例(mybatis创建)
		EmpMapper mapper = session.getMapper( EmpMapper.class );
		//调用EmpMapper子类实例的insert06方法,新增员工信息
		Map map = new HashMap();
		map.put( "name" , "张飞飞" );
		map.put( "job", "大数据开发工程师" );
		map.put( "salary", 20000 );
		mapper.insert06(map);
	}
	
	/* 练习18: 修改员工信息: 张飞 架构师 25000 */
	@Test
	public void testUpdate() {
		//获取EmpMapper接口的子类实例
		EmpMapper mapper = session.getMapper( EmpMapper.class );
		//将SQL语句中的参数封装到Emp对象中
		Emp emp = new Emp(null, "张飞飞", "数据分析师", 33000.0);
		//执行修改操作
		mapper.update07(emp);
	}
	
	@Test
	public void testDelete19() {
		//获取EmpMapper接口的子类实例
		EmpMapper mapper = session.getMapper( EmpMapper.class );
		//执行删除操作   id值
		mapper.delete19( 15 );
	}
}
//Mapper接口   我们写接口的声明,接口的实现类由框架实现8888888888
package com.tedu.dao;
import java.util.List;
import java.util.Map;
import com.tedu.pojo.Emp;
/** Mapper接口 
 * 接口的全限定接口名: com.tedu.dao.EmpMapper
 * 				   com.tedu.dao.EmpMapper
 */
public interface EmpMapper {
	/* 练习1: 查询emp中的所有员工信息 */
	public List<Emp> findAll01(); //查询有返回值,返回的是一条记录(返回值类型和ressulttype的值一致),查询的是单条返回的是emp对象,查询的是多条返回的是emp集合.  List集合,泛型是emp类型的员工表   sql语句有占位符就需要传参数.
	
	/* 练习6: 新增员工信息: 张飞 Java开发工程师 15000 */
	public void insert06( Map map );  //增,删,改 返回的值是影响的行数都是int值,也可以选择不接收. (因为日志信息里面会打印行数) 方法需不需要传参要看sql语句中有没有占位符,多个参数先把参数封装到Map集合中
	
	/* 练习7: 修改员工信息: 张飞 架构师 25000 */
	public void update07( Emp emp );
	
	/* 练习:根据id删除员工信息  */
	public void delete19( Integer id );	//如果sql语句的占位符只有一个可以直接传参,也不用Emp对象和Mape集合来封装了。
}
EmpMapper.xml  sql语句映射的文件
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper
	PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
	"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.tedu.dao.EmpMapper">
	<!-- 练习1: 查询emp中的所有员工信息 -->
	<select id="findAll01" resultType="com.tedu.pojo.Emp">
		select * from emp
	</select>
	<!-- 练习6: 新增员工信息: 张飞 Java开发工程师 15000 -->
	<insert id="insert06">
		insert into emp value(null, #{name}, #{job}, #{salary})
	</insert>
	<!-- 练习7: 修改员工信息: 张飞 架构师 25000 -->
	<update id="update07">
		update emp set job=#{job}, salary=#{salary}
		where name=#{name}
	</update>
	<!-- 练习:根据id删除员工信息 -->
	<delete id="delete19">
		delete from emp where id=#{id}
	</delete>
</mapper>

还可以使用注解开发:不用这个EmpMapper.xml 文件配置sql语句(注解开发是写在使用Mapper接口开发中的方法上)

缺点:注解是把sql语句写在程序中,将来要改需要改程序,重新编译重新发布。

优点:代码简单,开发效率更快一些,自己不用再写xml文件 改动不频繁时可以使用注解方式。

//mybatis-config.xml核心配置文件
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE configuration
    PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
    "http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
	<!-- 0.导入jdbc.properties文件 -->
	<properties resource="jdbc.properties"/>
	
	<!-- 1.配置开发环境: 
		id属性是为当前环境指定一个名称,default属性指定使用哪一个环境 -->
	<environments default="test">
		<environment id="test">
			<!-- 1.1.配置事务管理方式  type="JDBC|MANAGED" 
				JDBC:将事务交给JDBC管理   MANAGED:自己管理事务 -->
			<transactionManager type="JDBC" />
			<!-- 1.2.配置数据源(连接池) type:指定连接池方式, 
				POOLED:使用连接池, UNPOOLED表示不使用连接池, JNDI:不常用 -->
			<dataSource type="POOLED">
				<property name="driver" value="${db.driver}"/>
				<property name="url" value="${db.url}"/>
				<property name="username" value="${db.username}"/>
				<property name="password" value="${db.password}"/>
			</dataSource>
		</environment>
	</environments>
	
	<!-- 2.扫描mapper接口所在的包(因为现在是将SQL语句通过注解直接写在接口方法上) 
	只要配置包扫描mybatis框架就知道你的sql语句是写在maper接口而不是在xml文件里面,他就会扫描sql注解获取里面的sql语句在执行-->
	<mappers>
		<package name="com.tedu.dao"/>
	</mappers>

</configuration>

Mapper接口:

package com.tedu.dao;

import java.util.List;
import java.util.Map;

import org.apache.ibatis.annotations.Delete;
import org.apache.ibatis.annotations.Insert;
import org.apache.ibatis.annotations.Select;
import org.apache.ibatis.annotations.Update;

import com.tedu.pojo.Emp;

/** Mapper接口 
 * 接口的全限定接口名: com.tedu.dao.EmpMapper
 * 				   com.tedu.dao.EmpMapper
 */
public interface EmpMapper {
	/* 练习1: 查询emp中的所有员工信息 */
	@Select("select * from emp")
	public List<Emp> findAll01();
	
	/* 练习6: 新增员工信息: 张飞 Java开发工程师 15000 */
	@Insert("insert into emp value(null,#{name},#{job},#{salary})")
	public void insert06( Map map );
	
	/* 练习7: 修改员工信息: 张飞 架构师 25000 */
	@Update("update emp set job=#{job},salary=#{salary} where name=#{name}")
	public void update07( Emp emp );
	
	/* 练习:根据id删除员工信息  */
	@Delete("delete from emp where id=#{id}")
	public void delete19( Integer id );	
}

1.8 几个可以优化的地方


1.8.1 加入log4j日志框架

mybatis默认整合了 log4j.,只需要 在项目中加入log4j的配置文件,就可以打印日志信息,便于开发调试。

在src/main/resources(或相似的目录)右键new —file—下创建log4j.properties如下:

这个文件名必须是log4j.properties, 必须放在这个目录resources.

# Global logging configuration    指定日志信息输出的位置  stdout:输出控制台. DEBUG日志信息的输出级别
log4j.rootLogger=DEBUG, stdout
# Console output... 指定用哪个Appender   相当于一个笔
log4j.appender.stdout=org.apache.log4j.ConsoleAppender
#后面2指定输出日志信息的格式
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern=%5p [%t] - %m%n

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

1.只要将该文件放在指定的位置,log4j工具会自动到指定位置加载上述文件,读取文件中的配置信息并使用! 指定后警告会消失了。

2.还需要导入jar包

1.8.2 SQL语句中的特殊符号

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

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

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

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

在这里插入图片描述

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

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

<select id="findBySal" resultType="com.tedu.pojo.Emp">
	select * from emp
	where 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 emp
	where 2=2 
	<if test="minSal != null">
		and salary >= #{minSal}
	</if>
	<if test="maxSal != null">
		and salary <![CDATA[ <= ]]> #{maxSal}
	</if>
</select>

1.8.3 jdbc.properties文件

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

(不用这种直接写在, mybatis的核心配置文件里也行)

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

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-GvDIO9p1-1638456215510)(JAVAWEB-NOTE04.assets/c689eb39f8139c834ce014974d446229.png)]

2、jdbc.properties文件内容如下: 这个文件是key----value结构

jdbc.driver=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/yonghedb?characterEncoding=utf-8
jdbc.username=root
jdbc.password=root
//这个前缀jdbc可以改,只不过需要和引入$ 里面的key保持一致才行

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

这个文件同样经过编译会放在类目录下,resource指的就是类目录下,所以直接传 把文件名复制过来就行。

保证占位符里面的key和这个properties文件里面的key保持一致.

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ZnD7nOuf-1638456215510)(JAVAWEB-NOTE04.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

1.9 扩展内容


1.9.1 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集合并返回
	 因为返回的是emp类型的集合,所以价格泛型
	 框架底层也是这样干的,先封装到一个对象,再把对象存到集合中.
	 */
	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方法
	@Override
	public String toString() {
		return "Emp [id=" + id + ", name=" + name + ", job=" + job + ", salary=" + salary + "]";
	}
}

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

如果在没有网络(外网)的情况下,编写mybatis-config.xml文件没有提示,可以按照下面的步骤进行配置: 这是用本地查找代替 从网上查找

(1)找到mybatis-3-config.dtd的文件的位置,例如: 这个是以前的图,现在在 day14-15mybatis中

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ubutjY9M-1638456215511)(JAVAWEB-NOTE04.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-XkvNsRPm-1638456215512)(JAVAWEB-NOTE04.assets/eef68594c0d7b072e09702b3d6289831.png)]

(4)在弹出的窗口中:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-TDSeUVui-1638456215513)(JAVAWEB-NOTE04.assets/364a97a96fcb74e6ca14d4a9c8b89e6b.png)]

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

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

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

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-fqJ2i9xg-1638456215513)(JAVAWEB-NOTE04.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-50mzcTdY-1638456215514)(JAVAWEB-NOTE04.assets/eef68594c0d7b072e09702b3d6289831.png)]

(4)在弹出的窗口中:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-c1lqZ4tP-1638456215515)(JAVAWEB-NOTE04.assets/e907b64d04d56389acba7c7e2f6bd74c.png)]

1.9.4 配置达内XML Schema代理服务器

1、打开Eclipse的配置首选项

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-KEnRxDJ1-1638456215515)(JAVAWEB-NOTE04.assets/eb81455caf93624c289a4e60105b6aca.png)]

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

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-H64IdH4o-1638456215516)(JAVAWEB-NOTE04.assets/bf2d8978925006db94220c283a695f57.png)]

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

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-fKbJfL5B-1638456215516)(JAVAWEB-NOTE04.assets/7e5343736b72ca605ac7e577025ccd9e.png)]

4、可以配置的代理有

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

2 spring框架

2.1 spring简介

2.1.1 什么是Spring?

轻量:指对代码的侵入性比较低.

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

SSH(struts2 spring hibernate) 基本上被 ssm取代了

SSM(springmvc spring mybatis)

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

解释: c: 代表controller,它底层是servlet

v:代表视图

M:代表model

spring三层都有涉及到.它们的对象是由Spring容器创建.前提是 mybatis需要和spring整合.springmvc不需要和spring整合.pojo也是三层都涉及到.(pojo封装请求参数传到数据库,数据库在把查询的到的数据封装到pojo对象传回去.)

分为三部分:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-qNi1YKaG-1638456215518)(JAVAWEB-NOTE04.assets/123.PNG)]

2.1.2 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通用版

2.1.3 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 的使用难度大为降低。

7).Spring框架源码是经典学习范例

Spring的源代码设计精妙、结构清晰、匠心独用,处处体现着大师对Java设计模式灵活运用以及对 Java技术的高深造诣。它的源代码无疑是Java技术的最佳实践的范例。

2.1.4 spring的架构

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

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-0eXYu1ql-1638456215518)(JAVAWEB-NOTE04.assets/bf0676465991176183ba7d0765728e57.png)]

组成 Spring 框架的每个模块(或组件)都可以单独存在,或者与其他一个或多个模块联合实现。每个模块的功能如下:

模块说明
核心容器Spring Core核心容器,提供Spring框架的基本功能。核心容器的主要组件是BeanFactory,它是工厂模式的实现。BeanFactory 使用控制反转(IOC)模式,将应用程序的配置和依赖性规范与实际的应用程序代码分开。
Spring ContextSpring上下文,是一个配置文件,向 Spring 框架提供上下文信息。Spring 上下文包括企业服务,例如 JNDI、EJB、电子邮件、国际化、校验和调度功能。
Spring AOP通过配置管理特性,Spring AOP 模块直接将面向切面的编程功能集成到了 Spring 框架中。可以很容易地使 Spring框架管理的任何对象支持AOP。Spring AOP模块为基于 Spring 的应用程序中的对象提供了事务管理服务。通过使用 Spring AOP,就可以将声明性事务管理集成到应用程序中。
Spring DAOJDBC DAO 抽象层提供了有意义的异常层次结构,可用该结构来管理异常处理和不同数据库供应商抛出的错误消息。异常层次结构简化了错误处理,并且极大地降低了需要编写的异常代码数量(例如打开和关闭连接)。Spring DAO 的面向 JDBC 的异常遵从通用的 DAO 异常层次结构。
Spring ORMSpring 框架插入了若干个 ORM 框架,从而提供了 ORM 的对象关系工具,其中包括JDO、Hibernate和iBatis SQL Map。所有这些都遵从 Spring 的通用事务和 DAO 异常层次结构。
Spring WebWeb上下文模块建立在应用程序上下文模块之上,为基于 Web 的应用程序提供了上下文。所以Spring 框架支持与 Jakarta Struts的集成。Web模块还简化了处理多部分请求以及将请求参数绑定到域对象的工作。
Spring MVC框架MVC 框架是一个全功能的构建 Web 应用程序的 MVC 实现。通过策略接口,MVC 框架变成为高度可配置的,MVC 容纳了大量视图技术,其中包括 JSP、Velocity、Tiles、iText 和 POI。

2.2 程序中的耦合和解耦

2.2.1 什么是程序的耦合

耦合性(Coupling),也叫耦合度,是对模块间关联程度的度量。耦合的强弱取决于模块间接口的复杂性、调用模块的方式以及通过界面传送数据的多少。模块间的耦合度是指模块之间的依赖关系,包括控制关系、调用关系、数据传递关系。模块间联系越多,其耦合性越强,同时表明其独立性越差(降低耦合性,可以提高其独立性)。耦合性存在于各个领域,而非软件设计中独有的,但是我们只讨论软件工程中的耦合。

总结:在软件工程中,耦合指的就是指对象之间的依赖关系。对象之间的依赖程度越高,耦合度就越高。对象之间的耦合越高,维护成本越高。因此对象的设计应使类和构件之间的耦合最小。

降低程序之间的依赖程度,即降低程序之间的耦合度的过程就叫做解耦。(不能完全消除)

例如:早期的Jdbc操作中,在注册数据库驱动时,为什么采用的是Class.forName的方式,而不是采用DriverManager.registerDriver的方式?

public class TestJdbc {
  public static void main(String[] args) throws Exception {
    //1.注册数据库驱动
    // DriverManager.registerDriver( new Driver() );
    Class.forName("com.mysql.jdbc.Driver");
    //2.获取数据库连接
    //3.获取传输器
    //4.发送sql到服务器执行并返回执行结果
    //5.处理结果
    //6.释放资源
  }
}



//依赖关系:比如在一个类里面依赖另一个类创建对象
class A{
    private B b=new B(); //A依赖B创建对象  这种设计依赖性太高一旦报错很多地方都要改,非常不合理(就像一个公司很依赖这个员工,一旦他离职公司可能就会瘫痪)
}
class B{
    
}

除了DriverManager.registerDriver会导致驱动注册两次外,更重要的是,如果使用这种方式,JDBC程序就会依赖于数据库的驱动类(MySQL的Driver类),如果后期程序因数据量和性能原因升级到Oracle数据库,就需要修改程序源代码——重新导入新的驱动类,这会增加很多不必要的麻烦!

而是用Class.forName方式注册驱动,这样的好处是Jdbc程序不再依赖具体的驱动类,即使删除(或不导入)mysql驱动包,程序依然可以编译(当然不可能运行,因为运行时肯定需要依赖驱动)。

此时类中仅仅是将mysql驱动类的全限定类名写死在程序中(只是一个字符串),可以将这个字符串提取到配置文件中,后期可以通过修改配置文件(而不用修改程序代码)轻松的替换数据库产品。

2.2.2 工厂模式解耦介绍

在实际开发中可以将三层(表现层、业务层、持久层)的对象都使用配置文件配置起来,当启动服务器加载应用时,可以通过工厂读取配置文件,根据配置文件中的配置将这些对象创建出来,在接下来使用的时候,直接拿过来使用即可。

那么,这个负责读取配置文件,根据配置文件创建并返回这些对象的类就是工厂。

可以通过【工厂+接口+配置文件】的方式解除程序中的耦合。

2.2.3 工厂模式解耦示例


解耦程序编写步骤:


1、创建一个Maven的Java工程(day16-spring)

2、创建持久层接口和接口实现类

com.tedu.dao.EmpDao (接口)

com.tedu.dao.EmpDaoImpl (实现类)

3、创建业务层接口和接口实现类

com.tedu.service.EmpService (接口)

com.tedu.service.EmpServiceImpl (实现类)

4、创建表现层测试程序(com.tedu.controller.EmpController)并运行测试程序

5、通过工程+配置文件+接口(已有)方式解耦

(1)创建工厂类(com.tedu.factory.BeanFactory)并实现

(2)提供配置文件,将service接口和dao接口的实现类的全限定类名编写到配置文件中。

6、使用工厂获取service接口和dao接口的实例,替换使用new的方式获取接口的实例。


详细代码如下:


1、创建持久层接口(com.tedu.dao.EmpDao)

package com.tedu.dao;
/**
 * 员工模块的Dao(持久层)接口
 */
public interface EmpDao {
	/** 添加员工信息 */
	public void addEmp();
}

2、创建持久层接口实现类(com.tedu.dao.EmpDaoImpl)

package com.tedu.dao;
/**
 * 员工模块的Dao(持久层)接口实现类
 */
public class EmpDaoImpl implements EmpDao {
	@Override
	public void addEmp() {
		System.out.println(
			"Dao层的addEmp()方法执行了..成功保存了一条员工信息.."
		);
	}
}

3、创建业务层接口(com.tedu.service.EmpService)

package com.tedu.service;
/**
 * 员工模块的service(业务层)接口
 */
public interface EmpService {
	/** 添加员工信息 */
	public void addEmp();
}

4、创建业务层接口实现类(com.tedu.service.EmpServiceImpl)

package com.tedu.service;
import com.tedu.dao.EmpDao;
import com.tedu.dao.EmpDaoImpl;
/**
 * 员工模块的service(业务层)接口实现类
 * service层 ---> dao层
 */
public class EmpServiceImpl implements EmpService {
	/* 获取Dao接口的子类实例
	 * ——这里使用new对象的方式造成了程序之间的耦合性提升 */
	private EmpDao dao = new EmpDaoImpl();
	
	@Override
	public void addEmp() {
		System.out.println("调用dao层的方法添加员工信息...");
		dao.addEmp();
	}
}

5、创建表现层测试类(com.tedu.controller.EmpController)

如果没有在pom文件添加测试依赖,可以直接把库加进来.

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-qA5sUfbX-1638456215519)(JAVAWEB-NOTE04.assets/cccc.PNG)]

package com.tedu.controller;
import org.junit.Test;
import com.tedu.service.EmpService;
import com.tedu.service.EmpServiceImpl;
/**
 * 模拟表现层 
 * Controller(表现层) --> Service(业务层) -->Dao(持久层)
 */
public class EmpController {
	/* 获取EmpService接口的子类实例
	 * 这里采用 new 的形式获取Service层的对象,提升了对象之间的依赖关系,也就是提升了耦合度!
	 * 如果 EmpServiceImpl 这个类一旦被替换,下面这行代码将会出现编译错误(整个项目中可能会
	 * 	出现很多类似于这样的错误,就需要修改程序,改完后还需要重新编译、重新发布,会提高项目的
	 * 	维护成本,造成很多不必要的麻烦!!) 
	 */
	//private EmpService service = new EmpServiceImpl(); new的方式
    EmpService service = (EmpService)BeanFactory.getBean( "EmpService" );//获取哪个接口子类的实例就把那个接口名传进去  返回值是object,现在我们用 EmpService接口来接收,所以需要强转   优势:如果我们需要替换掉这个子类,只需改变配置文件的类名就行,不用再像new对象改代码
	
	@Test
	public void testAddEmp() {
		System.out.println("调用service层的方法添加员工信息...");
		service.addEmp();
	}
}

在上面的程序中,EmpController中要调用Service层的方法,所以通过new对象的形式获取了EmpService接口子类的实例,代码如下:

private EmpService service = new EmpServiceImpl();

在EmpService的实现类中要调用Dao层的方法,所以通过new对象的形式获取了EmpDao接口子类的实例,代码如下:

private EmpDao dao = new EmpDaoImpl();

如果在上面的程序中将EmpDaoImpl或者EmpServiceImpl移除,会导致其他类中的代码编译错误。此时表现层和业务层,及业务层和持久层之间的依赖程度过高,如果将来替换某一层,很可能会造成其他层无法运行,只能通过修改程序代码保证程序运行,这样依赖就会提高维护成本以及造成不必要的麻烦。

在程序中new对象的方式造成了这种程序之间的依赖程度提升,即提升了程序之间的耦合性。

使用工厂+配置文件+接口 解耦 代码如下


6、创建com.tedu.factory.BeanFactory类,用于创建各个层所需要的对象。

package com.tedu.factory;

import java.io.InputStream;
import java.util.Properties;
/**
 * ---------------------------------------------------------
 * Bean: 可重用组件(计算机英语)
 * JavaBean:使用Java语言编写的可重用组件,例如:service层、dao层等
 * JavaBean:通常分为业务Bean和实体Bean
 * 		业务Bean:处理业务逻辑,service层、dao层
 * 		实体Bean:封装数据,例如,为了封装员工信息而编写的Emp实体类.
 * ---------------------------------------------------------
 * 解除耦合:
 * 模拟spring容器如何创建实例
 * (1)需要提供配置文件,在配置文件中配置service和dao的实现类
 *     配置内容为:唯一标识=实现类的全限定类名(key=value结构)
 * (2)通过工厂读取配置文件中配置的全限定类名,利用反射创建对象。
 * 		xml配置文件、properties配置文件
 */
/**
 * 模拟spring容器如何创建实例
 * (1) 提供一个config.properties配置文件,在这个文件中配置接口和实现类对应关系
 * 		EmpService=com.tedu.EmpServiceImpl 底层会通过反射创建该类的实例
 * (2) 在当前类中读取 config.properties文件中的配置信息,将文件中的所有配置读取到
 * 		Properties对象中
 * (3) 提供一个getBean方法,接收接口名(例如:EmpService),根据接口名到Properties
 * 		对象中获取该接口对应的实现类的全限定类名(com.tedu.EmpServiceImpl),
 * 		在基于反射创建该类的实例,作为方法的返回值直接返回即可!
 * 
 */

public class BeanFactory {
	//声明一个Properties对象,用于加载config.properties文件中的所有内容,在静态代码块中对其进行初始化  读取配置信息有框架会自动读,没有框架手动读   
	private static Properties prop;
	static {
		try {
			//初始化prop 这个new对象 通常不会变所以依赖也没事  这个prop可以想象成key --value结构  保存的这个配置文件的内容
			prop = new Properties();
			//获取指向config.properties文件的流对象
            //一个类从硬盘上加载到内存中(通过类加载器)就可以获取到该类的字节码对象
			ClassLoader loader = BeanFactory.class.getClassLoader();//获取一个类加载器 不仅可以加载类也可以加载配置文件 888888888888   通过类的字节码对象获取类加载器
            //这个文件放在源码目录下经过加载放在类目录下,load方法加载的是类目录下,所以我们只需要直接把文件名传入进来就行  直接在文件上ctrl +c复制类名
			InputStream in = loader.getResourceAsStream("config.properties");
			//将config.properties文件中的所有内容加载prop对象中
			prop.load( in );
			
		} catch (Exception e) {
			e.printStackTrace();
		}
	}
	
	/**
	 * 根据接口名,到config.properties文件中获取该接口对应子类的全限定类名(即根据key获取value)
	 * 	再基于反射创建该子类的实例
	 * @param interName 接口名(也是config.properties文件中的key)
	 * @return Object 根据传入的接口名,返回该接口对应的子类实例
	 */
	public static Object getBean(String interName) {//改为静态的方法直接调用
		try {
			//根据接口名,到config.properties文件中获取该接口对应子类的全限定类名
			String className = prop.getProperty( interName );
			//通过子类的全限定类名获取该类的字节码对象
			Class clz = Class.forName(className);
			//在通过该类的字节码对象,获取该类的实例  返回值是一个Object对象
			Object obj = clz.newInstance();
			return obj;
		} catch (Exception e) {
			e.printStackTrace();
		}
		return null;  //这个是因为如果程序执行时 从获取字节码对象开始抛异常了程序不会接着执行为null,添加一个return保证返回一个null值。他俩只能有一个执行,程序抛出异常就执行return  null反之则不执行
	}

	
	
}

7、在源码目录 src/main/resources下创建一个config.properties文件,文件内容配置如下:

new — File 配置接口 和实现类的关系

EmpService=com.tedu.service.EmpServiceImpl
EmpDao=com.tedu.dao.EmpDaoImpl

8、将EmpController类中通过 “new对象的形式获取了EmpService接口子类的实例” 以及在EmpServiceImpl类中通过 “new对象的形式获取了EmpDao接口子类的实例” 改为使用BeanFactory工厂获取Service和Dao层的实例。如下:

/* 获取Service接口的子类实例
 * ——这里使用new对象的方式造成了程序之间的耦合性提升 */
//private EmpService service = new EmpServiceImpl();
private EmpService service = (EmpService)BeanFactory.getBean("EmpService");
/* 获取Dao接口的子类实例
 * ——这里使用new对象的方式造成了程序之间的耦合性提升 */
//private EmpDao dao = new EmpDaoImpl();
private EmpDao dao = (EmpDao)BeanFactory.getBean( "EmpDao" );

2.4 Spring IOC控制反转


2.4.1 什么是控制反转

IOC(Inverse Of Control)控制反转,即**,把创建对象的权利交给框架。**

也就是指将对象的创建、对象的存储、对象的管理交给了spring容器。

(spring容器是spring中的一个核心模块,用于管理对象,底层可以理解为是一个map集合)

spring整合了mybaitis之后,mybatis的框架对象也由spring容器对象创建.


[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-1EDkG9ej-1638456215520)(JAVAWEB-NOTE04.assets/0a11c44a30f5a7328589e4031d624ffd.png)]


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

/* 获取Service接口的子类实例
 * ——这里使用new对象的方式造成了程序之间的耦合性提升 */
private EmpService service = new EmpServiceImpl();
//private EmpService service = (EmpService)BeanFactory.getBean("EmpService");

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

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

/* 获取Service接口的子类实例
 * ——这里使用new对象的方式造成了程序之间的耦合性提升 */
//private EmpService service = new EmpServiceImpl();
private EmpService service = (EmpService)BeanFactory.getBean("EmpService");//这个是模拟spring容器底层是如何创建对象的

只需要将类提前配置在配置文件中,就可以将对象的创建交给框架来做。当需要对象时,不需要自己创建,而是通过框架直接获取即可,省去了new对象的过程,自然就降低类和类之间的依赖关系,也就是耦合性。

2.5 IOC入门案例


下面将使用spring的IOC解决程序间的耦合

2.5.1 创建工程,引入jar包

创建Maven工程,引入spring相关依赖包

1、创建Maven—Java工程

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-IGTleAoP-1638456215520)(JAVAWEB-NOTE04.assets/e38fbdb8819178517de105a52e112a84.png)]

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

<dependencies>
	<!-- 添加junit的jar包 -->
	<dependency>
		<groupId>junit</groupId>
		<artifactId>junit</artifactId>
		<version>4.10</version>
	</dependency>
	<!-- 添加spring的jar包 -->
	<dependency>
		<groupId>org.springframework</groupId>
		<artifactId>spring-context</artifactId>
		<version>4.1.3.RELEASE</version>
	</dependency>
</dependencies>

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

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Y6ZT6q8V-1638456215521)(JAVAWEB-NOTE04.assets/ffe9287f31af4c4bf68f5d098b1d2039.png)]

2.5.2 创建spring配置文件

创建spring核心配置文件—applicationContext.xml

1、在工程的src/main/resources源码目录下,创建applicationContext.xml文件:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-XcBg47Sf-1638456215521)(JAVAWEB-NOTE04.assets/3b521e8ba5d563ec21a2e142fa6cf623.png)]

2、在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"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans.xsd">
	
	
</beans>

3、将EmpService接口的实现类的实例
以及EmpDao接口的实现类的实例交给Spring容器创建,在核心配置文件中添加如下配置:(通过配置文件可以告诉spring容器需要他帮我们创建那些对象)

<?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/beans
        http://www.springframework.org/schema/beans/spring-beans.xsd">
	
	<!-- 将EmpService接口的子类作为bean(bean就是对象),声明到spring容器中
		(即由spring容器创建该类的实例)  如果标签没有内容可以写成自闭也可以不写成自闭
		id属性的值就是一个独一无二的编号,用于获取当前类的实例  map中的key
(id值取名,如果这个类有接口一般用接口名,没有接口用类名,首字母变小写)
		class属性的值是一个类的全限定类名,可以让框架在底层通过反射创建该类的实例
  对象是以value属性存到map中,id对应的就是key  按着ctrl键有下划线说明写对了
类似之前配置的:
		EmpService=com.tedu.EmpServiceImpl  (接口名=子类的全称限定类目)
        这个bean标签.
		 -->
	<bean id="empService" class="com.tedu.EmpServiceImpl01"></bean>
	
	<!-- 将User作为bean声明到spring容器中,由spring容器创建该类的实例  scope属性用于创建多对象-->
	<bean id="user" class="com.tedu.pojo.User" scope="prototype">
		<!-- 通过set方法为User对象的属性赋值: setName("韩少云");setAge(18);
			setInfo(通过userInfo获取的对象);只要配置了这行信息,spring框架在底层就会调用setname方法,给name属性赋值,值通过配置的value指定.  它跟属性无关只跟对应的属性有关
		<property name="name" value="韩少云" /> 表示底层调的是setName方法,赋值为韩少云
		<property name="age" value="18" /> 标签里面没东西可以自闭也可以不自闭
		<property name="info" ref="userInfo"  value和ref不能重用   (如果是字符串和数值类型用value属性,如果属性是对像类型同ref,ref指向bean标签的id值.将来通过id即key去map集合取值,取得就是userinfo对象在赋值给info对象,当然赋值的也是setinfo方法)/>
		name里面的值如:name和属性里面的值名字没有关系,不一样仍可以赋值 ,它是和setName方法名有关系的-->
		<!-- 通过构造方法为User对象的属性赋值: 要求: name属性指定的值 要和
			构造函数中形参的名字保持一致! 写三个标签代表 有三个参数的构造函数匹配  -->
		<constructor-arg name="age" value="28"/>
		<constructor-arg name="name" value="马云"/>
		<constructor-arg name="info" ref="userInfo"/>
	</bean>
	
	<!-- 将UserInfo作为bean声明到spring容器中,spring容器就会创建userInfo类的实例存到map集合中 .key就是userinfo,value就是com.tedu.pojo.UserInfo的值-->
	<bean id="userInfo" class="com.tedu.pojo.UserInfo"></bean>	
</beans>

2.5.3 创建测试类进行解耦

创建测试类—TestSpring,通过spring的IOC解决程序中的耦合问题

1、创建测试类

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-D0rioj7f-1638456215521)(JAVAWEB-NOTE04.assets/30070bf2108c5dcd69ad97c00021d08b.png)]

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

package com.tedu.spring;

import org.junit.Test;
import org.springframework.context.support.ClassPathXmlApplicationContext;

import com.tedu.EmpService;
import com.tedu.pojo.User;

/**
 * 测试spring的IoC(控制反转)
 * 测试spring的DI(依赖注入)
 */
public class TestSpring {
	
	/* 1、测试spring的IoC(控制反转) 
	 * 2、测试spring的单实例和多实例
	 * 	单实例:通过spring容器多次获取同一个类的实例,spring容器从始至终只会为该类创建一个实例
	 * 		优势:可以节省内存空间,减少资源浪费
	 * 		缺点:由于每次获取的都是同一个对象,访问的也是同一个对象的成员,可能会引发线程安全问题。
	 * 	多实例:通过spring容器多次获取同一个类的实例,spring容器每次都会为该类创建新的实例
	 * 		优势:不会出现线程安全问题
	 * 		缺点:耗费内存空间
	 * 	总结:在保证不会出现线程安全问题的前提下,推荐使用单实例,否则使用多实例!
	 */
	@Test
	public void testIoC() {
		//获取的spring的容器对象(需要读一个spring容器的配置文件,文件名通常叫applicationContext.xml 也有人叫 bean.xml)      配置文件的位置  这个容器会扫描配置文件需要配置的对象,它的底层是一个map   这个文件名也是编译放在类目录下的根目录下,所以直接写类名
		ClassPathXmlApplicationContext ac = 
				new ClassPathXmlApplicationContext("applicationContext.xml");
		//通过spring容器获取EmpService接口的子类实例   如果希望spring容器帮你创建一个类的对象,需要将这个类作为一个bean声明到spring容器   返回的类型是object需要强转
		EmpService service = (EmpService)ac.getBean( "empService" );
		System.out.println( service );//打印的是对象的地址值说明打印成功了.
		
		//通过spring容器获取User类的实例(这个对象叫做bean对象)
		User user1 = (User)ac.getBean( "user" );
		User user2 = (User)ac.getBean( "user" );
		/* Bean对象的单实例:
		 * 	如果user1==user2,则说明user1和user2指向的是同一个对象
		 * 	spring容器为User类从始至终只创建了一个实例  (服务器关闭实例销毁)
		 * Bean对象的多实例:  在bean标签上加个属性 scope属性
		 * 	如果user1!=user2,则说明user1和user2指向的是不同的对象
		 * 	spring容器为User类每次都会创建不同的实例  */
		System.out.println( user1 == user2 );
	}
	
	/*
	 * 3、测试spring的DI(依赖注入)
	 * 	DI: 在通过spring容器创建对象的同时或者创建对象之后,由spring框架为对象的属性赋值
	 * 	(1)set方法注入: 调用对象的setXxx方法为xxx属性赋值
	 * 		例如: 调用user.setName()为name属性赋值
	 * 	(2)构造方法注入: 在spring容器创建对象的同时,通过有参构造函数为对象的属性赋值
	 * 		例如: User user = new User("韩少云", 18);
	 * 		为user对象的name属性和age属性赋值 */
	@Test
	public void testDI() {
		//获取Spring的容器对象
		ClassPathXmlApplicationContext ac = 
				new ClassPathXmlApplicationContext("applicationContext.xml");
		//通过spring容器获取User类的实例
		User user = (User)ac.getBean( "user" );
		//set方法注入
		System.out.println( user );
	}
	
	
}


//提供一个User类,里面有成员变量, 构造方法,get和set方法, toString方法

3、运行结果:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-b3Rku89o-1638456215522)(JAVAWEB-NOTE04.assets/96cee091183ae525af0bf5314bf057c1.png)]

4、入门案例总结:

这就是spring框架的IOC——控制反转。之前我们自己new对象,例如:

User u = new User();

而现在,变成由一个初始化的xml配置文件来创建,也就是由spring容器来创建。

User user = (User) ac.getBean(“user”);

当程序运行,spring开始工作后,会加载整个xml核心配置文件,读取到,获取到class属性中类的全路径,利用反射创建该类的对象。

2.6 Bean对象的单例和多例


2.6.1 Bean对象的单例和多例概述

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

最常用是singleton(单例)或prototype(多例)。其含义如下:

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

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

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

当把一个 bean 定义设置scope为singleton作用域时,Spring IOC容器会在每一次获取当前Bean时,都会产生一个新的Bean实例(相当于new的操作)
prototype只负责对象的创建和初始化,不负责销毁。

2.6.2 为什么用单实例或多实例?

之所以用单实例,在没有线程安全问题的前提下,没必要每个请求都创建一个对象,这样子既浪费CPU又浪费内存;

之所以用多例,是为了防止并发问题;即一个请求改变了对象的状态(例如,可改变的成员变量),此时对象又处理另一个请求,而之前请求对对象状态的改变导致了对象对另一个请求做了错误的处理;

用单例和多例的标准只有一个:当对象含有可改变的状态时(更精确的说就是在实际应用中该状态会改变),使用多实例,否则单实例;

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

单例:

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

多例:

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

2.6.3 测试spring的单实例和多实例

1、创建TestIOC2类,测试代码如下:

package com.tedu.spring;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class TestIOC2 {
	public static void main(String[] args) {
		/* 通过spring容器创建创建User对象,
		 * 并调用User中的sayHi方法  */
		//1.加载spring的核心配置文件
		ClassPathXmlApplicationContext ac = 
				new ClassPathXmlApplicationContext(
					"applicationContext.xml"
				);
		//2.从spring容器中获取bean对象(而不是自己new)
		User user1 = (User) ac.getBean("user");
		User user2 = (User) ac.getBean("user");
		
		//3.测试
		if(user1 == user2){
			System.out.println("当前实例为单实例...");
		}else{
			System.out.println("当前实例为多实例...");
		}
	}
}

3、将applicationContext.xml中,User类bean标签的scope值设置为singleton:

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

运行TestIOC2,运行结果为:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-gwjaL0iU-1638456215522)(JAVAWEB-NOTE04.assets/4f79acd66228d70451ef21a27911d21b.png)]

4、将applicationContext.xml中,User类bean标签的scope值修改为prototype

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

再次运行TestIOC2,运行结果为:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-I8cFOSYI-1638456215522)(JAVAWEB-NOTE04.assets/805f9db5f3eed078dc77876db32298ac.png)]

2.7 Spring DI依赖注入


2.7.1 两种注入方式介绍

DI(Dependency Injection)依赖注入 。 Injection:注入

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


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

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

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

或者:

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

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

(1).Set方式注入

(2).构造方法注入

2.8 set方式注入

2.8.1 普通属性注入

需求:通过Spring创建User实例,并为User实例的name和age属性(普通属性)赋值

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

package com.tedu.spring;

public class User {
	private String name;
	private Integer age;
	
	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;
	}
	
	@Override
	public String toString() {
		return "User [name=" + name + ", age=" + age + "]";
	}
}

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

<!-- 声明User类的bean实例 -->
<bean id="user" class="com.tedu.spring.User"></bean>

3、创建测试类—TestDI

package com.tedu.spring;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class TestDI {
	public static void main(String[] args) {
		//1.加载applicationContext.xml核心配置文件
		ClassPathXmlApplicationContext ac = 
				new ClassPathXmlApplicationContext(
					"applicationContext.xml"
				);
		//2.获取User类的实例
		User user = (User) ac.getBean("user");
		
		//3.输出User实例
		System.out.println( user );
	}
}

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

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-SkmSpy2d-1638456215523)(JAVAWEB-NOTE04.assets/1175a00c572e203e5413862754583a75.png)]

4、修改applicationContext.xml中User实例的声明,为User实例注入属性

<!-- 声明User类的bean实例 -->
<bean id="user" class="com.tedu.spring.User">
	<!-- 通过set方式为普通属性赋值 -->
	<property name="name" value="韩少云"></property>
	<property name="age" value="20"></property>
</bean>

其中name属性的值,必须要和User类中所注入属性 【对应的set方法名去掉set后、首字母变为小写的名字】 相同。

例如:为 User类中的age属性赋值,由于name属性对应的set方法名字为setAge,seAge去掉set和首字母变为小写后的名称为age,因此为age属性注入的配置内容为:

<property name="age" value="20"></property>

普通属性直接通过value注入即可。

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

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

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-3hs1dZPc-1638456215524)(JAVAWEB-NOTE04.assets/abda0664c744fdef3e8ece4ea2db2a45.png)]

2.8.2 对象属性注入

需求:通过Spring创建User实例,并为User对象的userInfo属性(对象属性)赋值

1、创建UserInfo类

package com.tedu.spring;
public class UserInfo {
	
}

2、在applicationContext.xml中,声明UserInfo类的bean实例

<!-- 声明UserInfo类的bean实例 -->
<bean id="userInfo" class="com.tedu.spring.UserInfo"></bean>

3、修改User类,声明userInfo属性,添加对应的setter和getter方法,并重新生成toString方法

public class User {
	...
	private UserInfo userInfo;
	
	public UserInfo getUserInfo() {
		return userInfo;
	}
	public void setUserInfo(UserInfo userInfo) {
		this.userInfo = userInfo;
	}
	...
	public String toString() {
		return "User [name=" + name + ", age=" + age + ", userInfo=" + userInfo + "]";
	}
}

4、在applicationContext.xml中,将UserInfo对象作为值,赋值给User对象的userInfo属性

<!-- 声明User类的bean实例 -->
<bean id="user" class="com.tedu.spring.User">
	<!-- 通过set方式为普通属性赋值 -->
	<property name="name" value="韩少云"></property>
	<property name="age" value="20"></property>
	<!-- 通过set方式为对象属性赋值 -->
	<property name="userInfo" ref="userInfo"></property>
</bean>

由于此处是将UserInfo对象作为值赋值给另一个对象的属性,因此ref属性的值,为UserInfo对象bean标签的id值。

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

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

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-8BgicHdt-1638456215524)(JAVAWEB-NOTE04.assets/62c1243d1a13010db1a3c3cd8b116759.png)]

2.9 构造方法注入


需求:通过Spring创建User对象,并为User对象的属性(name、age、UserInfo属性)赋值

1、为User类声明构造函数

//声明无参构造函数
public User() {
}
//声明有参构造函数
public User(String name, Integer age, UserInfo userInfo) {
	super();
	this.name = name;
	this.age = age;
	this.userInfo = userInfo;
}

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

<bean id="user" class="com.tedu.spring.User">
	<!-- 通过set方式为普通属性赋值
	<property name="name" value="韩少云"></property>
	<property name="age" value="20"></property>
	<property name="userInfo" ref="userInfo"></property>
	 -->
	<!-- 通过构造器中参数为属性赋值 -->
	<constructor-arg name="name" value="马云"></constructor-arg>
	<constructor-arg name="age" value="35"></constructor-arg>
	<constructor-arg name="userInfo" ref="userInfo"></constructor-arg>
</bean>

<!-- 声明UserInfo类的bean实例 -->
<bean id="userInfo" class="com.tedu.spring.UserInfo"></bean>

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

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

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

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

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-MKWrVqjU-1638456215525)(JAVAWEB-NOTE04.assets/4b1ba956af8e1244bd1a30553f4fe6bf.png)]

2.10 扩展内容


2.10.1 applicationContext.xml没有提示的解决办法

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

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

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-U0Jbp8vh-1638456215527)(JAVAWEB-NOTE04.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-qR2HfJ1c-1638456215528)(JAVAWEB-NOTE04.assets/eef68594c0d7b072e09702b3d6289831.png)]

(4)在弹出的窗口中:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-QX8APka1-1638456215529)(JAVAWEB-NOTE04.assets/10a6f5e3901af4d2fd9cba0d644e00bd.png)]

2、配置spring-context-4.1.xsd文件

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

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ZeAQLO6M-1638456215530)(JAVAWEB-NOTE04.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-dM5HLRW9-1638456215530)(JAVAWEB-NOTE04.assets/eef68594c0d7b072e09702b3d6289831.png)]

(4)在弹出的窗口中:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-lp2NQy2m-1638456215531)(JAVAWEB-NOTE04.assets/bab1eb1bf264e64de4a3672c96f23e8b.png)]

3 springmvc框架

3.1 MVC设计模式


3.1.1 什么是设计模式 参考day14–day18 springmvc

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

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

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

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

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

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

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

3.1.2 MVC设计模式

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

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

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

(2)封装数据处理数据的模型(Model) javaBean 在MVC设计模式下对应的是模型(Model),但不能说Model是javaBean 在不同的设计模式下对应是不同的.如不能说动物是猫.

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

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

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

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-OCCU9LAV-1638456215532)(JAVAWEB-NOTE04.assets/20e60e831a34d3a4a68805e386aa1779.png)]

3.2 初识SpringMVC


3.2.1 Servlet的缺点

1、通常情况下,一个Servlet类只负责处理一个请求,若项目中有成百上千个请求需要处理,就需要有成百上千个Servlet类,这样会使得项目中Servlet类的个数暴增;(但是通过配置也可以访问多个请求:访问路径改为/ 如果配置别的路径标签.他优先执行别的路径.因为通配符优先级别最低*. 在web.xml文件)

而在springmvc中不需要写servlet,它帮你写好了.我们只需要写controller类.类中可以添加多个方法,一个方法可以处理一个请求.

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

controller类是一个普通类不需要在web.xml文件中配置.

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

springmvc有参数绑定机制很容易接受请求中的参数.

String value = request.getParameter(String name);

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

servlet相当于是一个管家 用来分配任务的 javaBean用来处理请求的

controller类是一个普通类可以进行单元测试.

这些缺点SpringMVC都会帮你解决.

3.2.2 SpringMVC简介

Springmvc是spring框架的一个模块,spring和springmvc无需中间整合层整合(来自以同一个公司,默认整合过了)

Springmvc是一个基于mvc的web框架

3.2.3 springmvc执行原理

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-CTQQbZGb-1638456215532)(JAVAWEB-NOTE04.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;

3.3 springmvc入门案例


需求:

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

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

3.3.1 创建Maven—web工程

1、通过Maven创建web工程

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ObhSWLRk-1638456215533)(JAVAWEB-NOTE04.assets/image-20200410140300719.png)]

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

<dependencies>
	<!-- 单元测试 -->
	<dependency>
		<groupId>junit</groupId>
		<artifactId>junit</artifactId>
		<version>4.10</version>
		
	</dependency>

	<!-- SpringMVC的jar包  (在导入springmvc的jar包同时,也导入了spring的基础运行jar包,因为springmvc在运行时需要依赖spring框架) -->
	<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>

3.3.2 在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来处理   ctrl+shift+t收索类-->
	<servlet>
		<servlet-name>springmvc</servlet-name>
		<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
		
		<!-- 配置springmvc核心配置文件的位置,默认Springmvc的配置文件是在WEB-INF目录下,默认的名字为springmvc-servlet.xml,如果要放在其他目录,则需要指定如下配置:
  contextConfigLocation是固定的名字,不能写错   classpath:指定的是文件的类目录  这个文件编译后就是放在类目录下,所以在这直接写类名
<param-name标签>里面的内容可以随意指定,但要求上下2个名字保持一致.
		-->
		<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>

解释/* (*是通配符)

拦截后会造成一个死循环.

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-pKoHHu6G-1638456215533)(yonghe-ssm.assets/aaaa.PNG)]

3.3.3 创建并配置springmvc-config.xml

直接复制下面配置文件的内容即可! 在resources目录下创建spring-mvc的核心配置文件

<?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/mvc
						http://www.springframework.org/schema/mvc/spring-mvc-4.0.xsd
						http://www.springframework.org/schema/beans
						http://www.springframework.org/schema/beans/spring-beans-4.0.xsd
						http://www.springframework.org/schema/context
          				http://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、@Component(想给类创建一个实例,但不知道属于那一层可以用这个)等注解,
		将会自动将类注册为bean 
	 -->
	<context:component-scan base-package="com.tedu.controller">
	</context:component-scan>
	
	<!-- 4.配置内部资源视图解析器
		prefix:配置路径前缀     jsp文件的根目录
		suffix:配置文件后缀    jsp文件的扩展名
		http://localhost:8080/day17-springmvc/hello
		执行testHello方法, 方法最后 return "home"; 其实就是跳转到名称为home的jsp文件
		/WEB-INF/pages/home.jsp  配置前缀后缀其实就是为了返回的home路径更简短  返回的home值其实就是jsp文件,它会在前面拼上 前缀,后面拼上后缀  就是jsp的访问路径
	 -->
	<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
		<property name="prefix" value="/WEB-INF/pages/"/>
		<property name="suffix" value=".jsp"/>
	</bean>
	
	
</beans>

3.3.4 创建并实现HelloController类

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

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-hfgTL42X-1638456215534)(JAVAWEB-NOTE04.assets/image-20200411120818696.png)]

2、实现HelloController类



package com.tedu.controller;

import java.text.SimpleDateFormat;
import java.util.Arrays;
import java.util.Date;

import org.springframework.beans.propertyeditors.CustomDateEditor;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.ServletRequestDataBinder;
import org.springframework.web.bind.annotation.InitBinder;
import org.springframework.web.bind.annotation.RequestMapping;

import com.tedu.pojo.User;

/* @Controller作用:(1)标识当前类属于controller层
 * 	(2)在spring容器扫描到 com.tedu.controller包的类时, 如果类上有
 * 	@Controller、@Service等注解时,就会将该类的实例交给spring容器创建
 
 用bean标签太麻烦,用包扫描的方式:如果当前类所在的包配置了spring容器包扫描,具有该注解的类,就会作为bean注册到spring容器中,由spring容器创建实例.
 */
@Controller
//@RequestMapping("/abc")
public class HelloController {
	
	/* 2.springmvc获取请求参数(参数绑定) -- 简单参数绑定
	 * 	/testParam01?name=刘沛霞&age=35&addr=天津  通过?拼上请求参数
	 * 	如何获取请求中携带的数据(即获取请求中的参数)?
	 * 	springmvc中提供了参数绑定机制: 
	 * 	如果是单个获取,可以在方法上声明和请求参数同等个数、并且名称相同的形参
	 * 	request.getParameter("name")它底层会调用这个方法,然后将获取到的值在传给形参 
	 *    写几个形参就接几个参数
	 */
    
    //注意方法名字的参数和访问路径的参数名字想不相同无所谓.
	@RequestMapping("/testParam01")
	public String testParam01(String name, Integer age, String addr) {
		System.out.println( "name="+name );
		System.out.println( "age="+age );
		System.out.println( "addr="+addr );
		return "home";
	}
	
	/* 3.简单参数绑定练习:
	 * 	/testParam02?username=赵云&psw=123&nickname=赵子龙
	 * 	添加controller方法,获取请求中的username、psw、nickname的参数值
	 */
	@RequestMapping("/testParam02")
	public String testParam02(String username,String psw,String nickname) {
		System.out.println( "username="+username );
		System.out.println( "psw="+psw );
		System.out.println( "nickname="+nickname );
		return "home";
	}
	
	/* 4.springmvc获取请求参数(参数绑定) -- 包装类型参数绑定
	 * 	/testParam03?name=刘沛霞&age=35&addr=天津
	 * 	如何获取请求中携带的数据(即获取请求中的参数)?
	 * 	springmvc中提供了参数绑定机制: 
	 * 	如果通过对象获取, 需要保证包装对象中有参数名对应的set方法 
	 * 提供一个User类 :为了接受和封装请求中的 数属性值,提供与之对应的set  get方法
	 *  访问时调的是set  get方法
     * 如果参数有很多个,或者就想用对象来封装参数,就用包装类型的参数绑定
     它底层会自动调用请求参数对应的在pojo对象的set方法,把请求参数封装到对象上,注意和之前封装查询数据库中表中数据的区别?
     mybatis,之前封装用的是 setxxx方法,如果没有直接给属性赋值(需要设置访问权限)
    springmvc, 现在封装请求参数是,只能用setxxx方法赋值.
	 */
	@RequestMapping("/testParam03")
	public String testParam03( User user ) {
		System.out.println( user );
		return "home";
	}
	
	/*  参数绑定的细节 :
	 * 5.1.如何获取一个参数名对应的多个参数值? 声明一个参数类型的数组来接受.(request有一个可以获取参数数组的方法,只需要定义一个参数数组类型的形参.通过参数绑定机制底层就会自动调用这个方法)
	 * 5.2.如何获取一个日期类型的参数? 可能存在什么问题? 如何解决? 只需要在方法上 声明一个Date类型的形参.
	 * 	/testParam04?like=篮球&like=足球&like=排球&date=2020/7/22 11:41:32
	 * 	/testParam04?like=篮球&like=足球&like=排球&date=2020-7-22 11:41:32
	 * 	String[] like = request.getParameterValues("like")
	 * 	400错误,是因为参数类型不匹配导致的,例如,使用Integer/int接收一个非数值
	 * 		或者使用日期对象接收一个非日期格式的字符串
	 * 	由于springmvc默认的日期格式是以 "/"(斜杠)分隔,所以当浏览器传递过来的日期是以斜杠
	 * 		分隔,springmvc可以通过参数绑定进行获取
	 * 	但如果浏览器传递过来的日期是以别的符号分隔(例如:-横杠),在springmvc看来这不是一个日期
	 * 		所以无法获取,就会报400错误!
	 * 	解决方法: (1)通过浏览器传递日期参数时,不要用横杠分隔,而是用斜杠(/)分隔
	 * 		(2)将springmvc默认的接收的日期格式改为以横杠(-)分隔! 使用,自定义日期格式转换器,在这个类中 写.
	 */
	@RequestMapping("/testParam04")
	public String testParam04( String[] like, Date date ) {
		System.out.println( Arrays.toString( like ) );
		System.out.println( date );
		return "home";
	}
	
	/* 1.快速入门
	 * @RequestMapping 是为当前类或者方法配置访问路径, 
	 * 	要求: 类上的访问路径+方法上的访问路径是不能重复的,否则就会抛异常!
	 *  类上如果有访问路径,要先加上类上的路径在加方法的访问路径
	 * 项目发布到服务器上,java文件资源文件不能直接访问 需要我们自己配置访问路径,先访问项目的路径在拼上配置的访问路径.   servlet可以直接访问: 右键---run  as  而jsp文件可以间接访问,通过在controller中写一个有返回值类型的方法,在类上方法上通过注解配置访问路径,方法的返回值跳转到jsp页面. 前提是在web.xml文件中拦截器 
	 项目发布到服务器上,java文件资源文件不能直接访问,servlet可以.
	 如何访问:在左侧的项目上右键--run as(这是在项目上发布到服务器,启动服务器) 然后再加上方法的访问路径就能访问这个类里面的方法.在通过这个返回值跳转到jsp.
	 web资源可以直接右键run--as
	 */
	@RequestMapping("/hello01")
	public String testHello01() {
		System.out.println("testHello01()...");
        //跳转到/WEB-INF/pages/home.jsp 
        //为什么写个home就能跳转到 具体目录的文件,因为在配置文件中配置了前缀后缀.
		return "home";
	}
	@RequestMapping("/hello02")
	public String testHello02() {
		System.out.println("testHello02()...");
		return "home";
	}
	
	
	/* 自定义日期格式转换器
	 * 将springmvc默认以斜杠(/)分隔日期改为以横杠分隔(-)
	 */
	@InitBinder
	public void InitBinder (ServletRequestDataBinder binder){
		binder.registerCustomEditor(java.util.Date.class, 
			new CustomDateEditor(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"), true)
		);
	}	
}


//usesr类 用来封装多个请求参数 省略  (注意这个pojo是封装的是请求参数,之前是查询数据库的记录)

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-wzxwZsFU-1638456215535)(yonghe-ssm.assets/bbbb.PNG)]

3.3.5 创建并实现home.jsp

在WEB-INF/pages/目录下,创建home.jsp页面。 右键先创建一个目录Folder在创建一个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>

3.3.6 访问测试

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

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-wSZdWVJO-1638456215535)(JAVAWEB-NOTE04.assets/image-20200411120516046.png)]

3.4 springmvc参数绑定


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

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

3.4.1 基本类型参数绑定

当需要获取客户端发送过来的少量数据时,可以在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-EnZGlTJj-1638456215536)(JAVAWEB-NOTE04.assets/image-20200411121233000.png)]

控制台输出结果为:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Zg1jgePf-1638456215536)(JAVAWEB-NOTE04.assets/image-20200411121322974.png)]

3.4.2 包装类型参数绑定

当需要获取客户端发送过来的多个数据时,可以在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;

/**
 * 封装用户信息  这个是多个请求参数封装在一个User对象中即包装类型的参数绑定,通过set和get方法进行访问.封装的参数名要有对应的set和get方法,有的话会封装成功,没有会封装失败.   mybatis查询的数据库中的数据封装到pojo对象中,可以通过set和get方法,如果没有还可以直接通过私有属性进行赋值. 而springmvc包装类型的参数绑定只能是通过set和get方法进行封装获取的请求参数的数据.
 */
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-kAqMPSM7-1638456215537)(JAVAWEB-NOTE04.assets/image-20200411125305873.png)]

控制台输出结果为:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-rk9GQ4PZ-1638456215537)(JAVAWEB-NOTE04.assets/image-20200411125340557.png)]

3.4.3 日期类型参数绑定

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参数一起发送给服务器:

控制台输出结果为:

常见问题:

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

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-8mg2UiAy-1638456215538)(JAVAWEB-NOTE04.assets/image-20200410165410638.png)]

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

2、解决方案:

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

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

3、再次测试:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-2N0sWatm-1638456215539)(JAVAWEB-NOTE04.assets/image-20200411125752937.png)]

控制台输出结果为:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-N8nZOErD-1638456215539)(JAVAWEB-NOTE04.assets/image-20200411125827915.png)]

3.5 跳转和乱码处理


3.5.1 实现转发(forward)

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

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

1、在HelloController中,提供testForward方法,代码实现如下:(前提是同一个web应用)

注意: java文件不能直接访问,只能通过类方法上配置注解来访问.

而其它的web-资源文件可以直接访问,但是如果在web-inf目录下不能直接访问(放在这个目录更加安全),放在web-inf资源的目录只能通过请求转发,重定向不行.

@Controller
public class HelloController02 {
	/* 1、测试springmvc的转发
	 * (1)如果在controller方法内部【 return “jsp的名字”】就是从当前方法
	 * 	转发到这个jsp (只有转发才能跳转到/WEB-INF目录中的资源) 这种形式默认就是转发
	 * (2)如果是从当前controller方法跳转到其他的controller方法
	 * 	此时需要 【return "forward:/资源路径"】
	 * 	例如: 当浏览器访问 "/testForward"时,testForward方法执行,在方法内部
	 * 	跳转到 "/test" 路径对应的方法(test方法)    forward:向前
	 */  Mapping:提供信息     /可以省略
	@RequestMapping("/testForward")
	public String testForward() {
		System.out.println("testForward()执行了...即将跳转到 /test路径..");
		return "forward:/test"; //跳转到了 test方法
	}
    @RequestMapping("/test")
	public String test() {
		System.out.println("test方法执行了...即将跳转到test.jsp...");
		//转发到test.jsp (默认就是转发,所以不需加forward)
		return "test"; //跳转到test.jsp, 这个也是转发,通过方法的返回值进行转发默认跳转到jsp页面,不需要再写forward.
	}
}

test,jsp文件

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
</head>
<body>
	<h1>day17-springmvc~~~test.jsp~~~</h1>	
</body>
</html>

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

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-zprNCdfP-1638456215540)(JAVAWEB-NOTE04.assets/image-20200411125952494.png)]

forward方式相当于:

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

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

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

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

3.5.2 实现重定向(redirect)

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

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

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

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

/* 2、测试springmvc的重定向
	 * (1)可以从当前controller中的方法重定向到另一个controller方法
	 * (2)也可以从当前controller方法重定向到其他应用的资源
	 * (3)也可以从当前controller方法重定向到其他服务器内部的资源
	 * 		【return "redirect:/资源路径"】
	 *    重定向地址栏路径会发生变化  由testRedirect 变为 test
	 */
	@RequestMapping("/testRedirect")
	public String testRedirect() {
		System.out.println("testRedirect方法执行了..即将跳转到 /test...");
重定向不同的方法		//return "redirect:/test"; //重定向到 /test
重定向不同的应与资源		//return "redirect:http://localhost:8080/day11-jsp/home.jsp";
重定向不同的服务器资源		return "redirect:http://www.baidu.com";
	}
	
	@RequestMapping("/test")
	public String test() {
		System.out.println("test方法执行了...即将跳转到test.jsp...");
		//转发到test.jsp   这个地方写转发只是为了测试重定向
		return "test"; //跳转到test.jsp
	}

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

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Hhbd1hup-1638456215540)(JAVAWEB-NOTE04.assets/image-20200411130114886.png)]

redirect方式相当于:

response.sendRedirect(url);

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

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

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

3.5.3 乱码处理

在前面的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>

测试类 中的方法: 使用Post提交 :提供一个html中的form表单提交参数

/* 3、测试springmvc的请求参数乱码处理
	 * (1)如果是GET提交、tomcat为8.0及以后的版本,GET提交的中文参数在获取时,是
	 * 	没有乱码的!
	 * (2)如果是POST提交,无论哪个版本的tomcat,POST提交的中文参数在获取时,都是
	 * 	有乱码的, request中的解决方法为: 在获取任何参数的代码之前,添加如下代码
	 * 		request.setCharacterEncoding("utf-8");
	 * 	如果是springmvc,解决post提交的中文参数乱码问题,只需要配置一个过滤器,就可以
	 * 	达到一劳永逸的效果.(对整个项目有效)
	 * 	需要在web.xml文件中启动springmvc的过滤器,解决POST提交的中文参数乱码问题!
	 * 	//获取请求中的 user和like对应的参数值 */
	@RequestMapping("/testParam05")
	public String testParam05(String user, String[] like) {
		System.out.println( "user="+user );
		System.out.println( "like="+ Arrays.toString( like ) );
		return "test";
	}

Servlet中,两种请求方式乱码解决方案回顾:(只对当前servlet有效,如果别的servlet也要获取那么还会有乱码问题.)

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

request.setCharacterEncoding("utf-8");

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

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-24aDzjVh-1638456215541)(JAVAWEB-NOTE04.assets/31a1d635302b07dcec001a8cb8a27a4f.png)]

3.6 springmvc响应数据


3.6.1 Model的使用

当请求发起访问Controller中的方法时,可以通过参数声明,在方法内使用Model。(从controller的方法跳转到jsp文件)

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

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

model.addAttribute(String name, Object value);

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

示例,往Model中添加属性:

/*
	 * 4、springmvc响应数据:
	 * 	如何从Servlet带数据到JSP?
	 * 		request域对象+请求转发
	 * 	如何从controller方法带数据到JSP? 
	 * 		Model(底层就是request域)+请求转发 */
	@RequestMapping("/testModel01")
	public String testModel01( Model model ) { //Model想用的时候可以直接声明调用
		//对请求进行处理,处理的结果是一个User对象
		//声明一个User对象,将User对象存入Model中  在User类中 声明一个有参的构造方法
		User user = new User("张三疯",30,"北京石景山");
		model.addAttribute( "user", user );
		//request.setAttribute( "user", user );
		//转发到test.jsp
		return "test"; 
	}
	@RequestMapping("/testModel02")
	public String testModel02( Model model ) {
		//对请求进行处理,处理的结果是一个List<User>对象
		//声明一个List集合,并将集合存入Model域中
		List<User> userList = new ArrayList();
		userList.add( new User("张三疯", 30, "北京石景山") );
		userList.add( new User("李四", 38, "北京昌平") );
		model.addAttribute( "list", userList );
		//request.setAttribute( "user", user );
		//转发到test.jsp
		return "test"; 
	} 

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

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
</head>
<body>
	<h1>day17-springmvc~~~test.jsp~~~</h1>
	<%-- ${}可以从 request域 中获取数据 对象的数据   底层是Map集合,通过key获取value--%>
	${ user.getName() }
	${ user.getAge() }
	${ user.getAddr() }
	<hr/>     <!--EL标签可以简写: -->
	${ user.name }
	${ user.age }
	${ user.addr }
	<hr/>
	<%-- 通过${}从request域中获取List集合   集合中的数据--%>
	${ list[0] } <br/>
	${ list[1] } <br/>
	
</body>
</html>

3.7 扩展内容


3.7.1 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/>

3.7.2 applicationContext.xml没有提示的解决方法

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

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

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-yT4nKBGM-1638456215542)(JAVAWEB-NOTE04.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-TSqxnod1-1638456215542)(JAVAWEB-NOTE04.assets/eef68594c0d7b072e09702b3d6289831.png)]

(4)在弹出的窗口中:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-XiKX8OGE-1638456215543)(JAVAWEB-NOTE04.assets/10a6f5e3901af4d2fd9cba0d644e00bd.png)]

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

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

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-RqOMXLzh-1638456215543)(JAVAWEB-NOTE04.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-G9FyvctI-1638456215544)(JAVAWEB-NOTE04.assets/eef68594c0d7b072e09702b3d6289831.png)]

(4)在弹出的窗口中:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-oYQ9QtNQ-1638456215544)(JAVAWEB-NOTE04.assets/bab1eb1bf264e64de4a3672c96f23e8b.png)]

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值