mybatis基础-配置开发环境

一、搭建数据源

mysql客户端连接mysql数据库,执行下面语句。【第一次写的时候,居然写成tbl_employee{},sql语句没有{},应该是()  】

CREATE TABLE tb1_employee (
   id INT(11) PRIMARY KEY AUTO_INCREMENT,
   last_name VARCHAR(255),
   gender CHAR(1),
   email VARCHAR(255)

)

因为不区分大小写,所以也可以采用如下写法:

create table tbl_employee(
  id int(11) primary key auto_increment,
  last_name varchar(255),
  gender char(1),
  email varchar(255)
)

创建完表格后,并在其中添加一条记录。

id	last_name    gender	     email
1	   mike	       0     [email protected]

二、创建项目代码

1、工程代码总览:

(代码包括mybatis 的两种操作 方法)

2、添加项目jar包

项目用到的jar包主要有:
mybatis-3.4.6.jar 【mybatis 包】
mysql-connector-java-5.1.18.jar 【mysql 驱动包】

额外添加的包:
mybatis-3.4.6-sources.jar 【mybatis 源码包,方便debug 跟踪代码】
mybatis-3.4.6-javadoc【mybatis 文档包,方便查看】
log4j-1.2.17.jar 【日志打印包,需要配合 log4j 的配置文件一起使用,这里用到的配置文件是log4j.xml

log4j.xml文件:

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE log4j:configuration SYSTEM "log4j.dtd">
 
<log4j:configuration xmlns:log4j="http://jakarta.apache.org/log4j/">
 
 <appender name="STDOUT" class="org.apache.log4j.ConsoleAppender">
   <param name="Encoding" value="UTF-8" />
   <layout class="org.apache.log4j.PatternLayout">
    <param name="ConversionPattern" value="%-5p %d{MM-dd HH:mm:ss,SSS} %m  (%F:%L) \n" />
   </layout>
 </appender>
 <logger name="java.sql">
   <level value="debug" />
 </logger>
 <logger name="org.apache.ibatis">
   <level value="info" />
 </logger>
 <root>
   <level value="debug" />
   <appender-ref ref="STDOUT" />
 </root>
</log4j:configuration>

3、创建ORM中的对象

package com.mybatis.bean;

public class Employee {
	private int id;
	private String lastName;
	private String email;
	private String gender;

	public int getId() {
		return id;
	}

	public void setId(int id) {
		this.id = id;
	}

	public String getLastName() {
		return lastName;
	}

	public void setLastName(String lastName) {
		this.lastName = lastName;
	}

	public String getEmail() {
		return email;
	}

	public void setEmail(String email) {
		this.email = email;
	}

	public String getGender() {
		return gender;
	}

	public void setGender(String gender) {
		this.gender = gender;
	}

	@Override
	public String toString() {
		return "Employee [id=" + id + ", lastName=" + lastName + ", email=" + email + ", gender=" + gender + "]";
	}

}

4、创建全局配置文件

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>
	<environments default="development">
		<environment id="development">
			<transactionManager type="JDBC" />
			<dataSource type="POOLED">
				<property name="driver" value="com.mysql.jdbc.Driver" />
				<property name="url" value="jdbc:mysql://localhost:3306/mybatis" />
				<property name="username" value="root" />
				<property name="password" value="Kitty521!" />
			</dataSource>
		</environment>
	</environments>
	<!-- 将我们写好的sql映射文件(EmployeeMapper.xml)一定要注册到全局配置文件(mybatis-config.xml)中 
	如果 数据库全局文件 和 子配置文件 不在同一个目录 ,就需要 /目录/目录/.../EmployeeMapper_old.xml
	-->
	<mappers>
	    <!-- 旧方法操作mybatis 需要 的配置文件 -->
		<mapper resource="EmployeeMapper_old.xml" />
	    <!-- 新方法操作mybatis 需要 的配置文件 -->
		<mapper resource="EmployeeMapper_new.xml" />
	</mappers>
</configuration>

5、旧方法操作mybatis

(1)对象映射配置文件:EmployeeMapper_old.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="com.mybatis.hhh.EmployeeMapper">
<!-- 
上面的 namespace:名称空间;名字随便写

下面的 id:唯一标识
下面的 resultType:返回值类型
下面的 #{id}:从传递过来的参数中取出id值
 -->
	<select id="selectEmp" resultType="com.mybatis.bean.Employee">
	<!-- select * from tb1_employee where id = #{id}  
	这样的查询结果导致数据库 last_name 无法赋值给employee bean中的lastName
	所以,employee 中的  lastName 是空值,解决版本是 查询结果 取 别名-->	
	select id ,last_name lastName,email, gender from tbl_employee where id = #{id}
	</select>
</mapper>

(2) JUnit 单元测试:

public class MybatisTest {

	/**
	 * 1、根据xml配置文件(全局配置文件)创建一个SqlSessionFactory对象 有数据源一些运行环境信息
	 * 2、sql映射文件;配置了每一个sql,以及sql的封装规则等。 
	 * 3、将sql映射文件注册在全局配置文件中
	 * 4、写代码:
	 * 		1)、根据全局配置文件得到SqlSessionFactory;
	 * 		2)、使用sqlSession工厂,获取到sqlSession对象使用他来执行增删改查
	 * 			一个sqlSession就是代表和数据库的一次会话,用完关闭
	 * 		3)、使用sql的唯一标志来告诉MyBatis执行哪个sql。sql都是保存在sql映射文件中的。
	 * 
	 * @throws IOException
	 */
	@Test
	public void test_old() throws IOException {
		String resource = "mybatis-config.xml";
		InputStream inputStream = Resources.getResourceAsStream(resource);
		SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);

		//获取sqlSession实例 ,能直接执行已经映射的sql语句。
		// sql的唯一标识:statement Unique identifier matching the statement to use.
		// 执行sql要用的参数:parameter A parameter object to pass to the statement.
		SqlSession openSession = sqlSessionFactory.openSession();
		try{
		//唯一标识符可以直接写 selectEmp 但为了防止 和别可能有的冲突,添加了命名空间
	    Employee employee =  openSession.selectOne("com.mybatis.hhh.EmployeeMapper.selectEmp",1);
	    System.out.println(employee);
		}catch (Exception e) {
			// TODO: handle exception
		}finally {
		    openSession.close();
		}
	}
}

6、新方法操作mybatis

(1)对象映射配置文件:EmployeeMapper_new.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="com.mybatis.mapper.EmployeeMapper">
<!-- 
上面的 namespace:名称空间;指定为接口的全类名
下面的 id:唯一标识被规定为接口方法名 【public Employee getEmpById(Integer id);】
下面的 resultType:返回值类型
下面的 #{id}:从传递过来的参数中取出id值
 -->
	<select id="getEmpById" resultType="com.mybatis.bean.Employee">
		select id,last_name lastName,email,gender from tbl_employee where id = #{id}
	</select>
</mapper>

(2)创建对象映射的接口文件

EmployeeMapper.java

package com.mybatis.mapper;

import com.mybatis.bean.Employee;

public interface EmployeeMapper {

	public Employee getEmpById(Integer id);
	
}

(3)JUnit 单元测试

/**
 * 1、接口式编程
 * 	原生:		Dao		====>  DaoImpl
 * 	mybatis:	Mapper	====>  xxMapper.xml
 * 
 * 2、SqlSession代表和数据库的一次会话;用完必须关闭;
 * 3、SqlSession和connection一样她都是非线程安全。每次使用都应该去获取新的对象。(就是不要放在共享成员变量里面,A线程用完释放,B线程再用就为空了)
 * 4、mapper接口没有实现类,但是mybatis会为这个接口生成一个代理对象。
 * 		(将接口和xml进行绑定)
 * 		EmployeeMapper empMapper =	sqlSession.getMapper(EmployeeMapper.class);
 * 5、两个重要的配置文件:
 * 		mybatis的全局配置文件:包含数据库连接池信息,事务管理器信息等...系统运行环境信息
 * 		sql映射文件:保存了每一个sql语句的映射信息:
 * 					将sql抽取出来。	
 *
 */
public class MybatisTest {

        public SqlSessionFactory getSqlSessionFactory() throws IOException {
	        String resource = "mybatis-config.xml";
	        InputStream inputStream = Resources.getResourceAsStream(resource);
	        return new SqlSessionFactoryBuilder().build(inputStream);
        }

        @Test
	public void test_new() throws IOException {
		// 1、获取sqlSessionFactory对象
		SqlSessionFactory sqlSessionFactory = getSqlSessionFactory();
		// 2、获取sqlSession对象
		SqlSession openSession = sqlSessionFactory.openSession();
		try {
			// 3、获取接口的实现类对象
			//会为接口自动的创建一个代理对象,代理对象去执行增删改查方法
			EmployeeMapper mapper = openSession.getMapper(EmployeeMapper.class);
			Employee employee = mapper.getEmpById(1);
			System.out.println(mapper.getClass());
			System.out.println(employee);
		} finally {
			openSession.close();
		}

	}

}

 


三、全局配置文件解析

1、配置标签顺序

在mybatis的全局配置文件中, 在 <configuration>标签下:

Content Model : (properties?, settings?, typeAliases?, typeHandlers?, objectFactory?, objectWrapperFactory?,

reflectorFactory?, plugins?, environments?, databaseIdProvider?, mappers?)

注意上面的子标签都是有顺序的

2、标签详解请看相关章节

 

四、映射文件解析

 


补充:

SqlSession 的实例不是线程安全的,因此是不能被共享的。

SqlSession每次使用完成后需要正确关闭,这个关闭操作是必须的

SqlSession可以直接调用方法的id进行数据库操作,但是我们一般还是推荐使用SqlSession获取到Mapper接口的代理类,执行代理对象的方法,可以更安全的进行类型检查操作

关于JUnit单元测试,只需在eclipse中新建 JUnit Test Case 就行了。

mybatis入门概述-mybatis框架简介

MyBatis简介

核心总结:

默认情况,自动生成的 SQL映射文件,默认 Mybatis的查询,只会返回字段给beanbean中的子对象是不存在的。

除非自己自定义bean中的子对象 ,和自定义SQL语句,并将sql语句返回的属性赋值给子对象的属性中。

事实情况举例如下:
{"code":100,"msg":"处理成功!","extend":{"emp":{"empId":1,"empName":"Jerry","gender":"M","email":"[email protected]","dId":1,"department":null}}}


原是Apache的一个开源项目iBatis, 2010年6月这个项目由Apache Software Foundation 迁移到了Google Code,随着开发团队转投Google Code旗下,iBatis3.x正式更名为MyBatis ,代码于2013年11月迁移到Github(下载地址见后)。

iBatis一词来源于“internet”和“abatis”的组合,是一个基于Java的持久层框架。iBatis提供的持久层框架包括SQL Maps和Data Access Objects(DAO)

MyBatis 是支持定制化SQL、存储过程以及高级映射的优秀的持久层框架。

MyBatis 避免了几乎所有的JDBC 代码和手动设置参数以及获取结果集。

MyBatis可以使用简单的XML或注解用于配置和原始映射,将接口和Java的POJO(Plain Old Java Objects,普通的Java对象)映射成数据库中的记录.

MyBatis用处

MyBatis是一个半自动化的持久化层框架。

•JDBC
–SQL夹在Java代码块里,耦合度高导致硬编码内伤
–维护不易且实际开发需求中sql是有变化,频繁修改的情况多见

•Hibernate和JPA
–长难复杂SQL,对于Hibernate而言处理也不容易
–内部自动生产的SQL,不容易做特殊优化。
–基于全映射的全自动框架,大量字段的POJO进行部分映射时比较困难。导致数据库性能下降。

•对开发人员而言,核心sql还是需要自己优化
•sql和java编码分开,功能边界清晰,一个专注业务、一个专注数据。

mybatis下载

1、https://github.com/mybatis/mybatis-3/

2、http://central.maven.org/maven2/org/mybatis/mybatis/3.4.6/

 

wordpress-网站迁移与本地主机测试

将网站定期备份到本地 ,方便本地调试 与 文件备份。 

一、网站备份迁移流程:

1、用mysql 客户端 备份数据库,导出sql

2、删除网站自动备份文件,将网站压缩打包,从服务器下载网站备份。

3、因为本地环境是Apache + php ,网站 环境是 nginx +php,所以需要迁移。

(1)开启本地mysql,导入网站 sql 数据,数据库名和原网站相同。

(2)配置本地虚拟主机,并将网站文件存放至虚拟主机目录。

(3)调整网站环境,解决其他迁移问题。


二、配置虚拟主机

mac os x已自带了apahce,so 我们不需要单独安装apache,只需修改其中配置即可。

1、修改apache主配置文件

sudo vim /etc/apache2/httpd.conf

搜索vhost关键字,将以下两行代码前的#删除

#LoadModule vhost_alias_module libexec/apache2/mod_vhost_alias.so
  
#Include /private/etc/apache2/extra/httpd-vhosts.conf

搜索php5关键字,将以下两行代码前的#删除

#LoadModule rewrite_module libexec/apache2/mod_rewrite.so

#LoadModule php5_module libexec/apache2/libphp5.so

wq保存退出,第一步完成。

2、修改虚拟主机配置文件

sudo vim /etc/apache2/extra/httpd-vhost.conf

将以下代码贴进文件尾行,并按照注释配置相关参数

<VirtualHost *:80>
    ServerAdmin [email protected]  //主机邮箱地址
    DocumentRoot "/usr/docs/dummy-host2.example.com"  //站点根目录
    ServerName dummy-host2.example.com  //站点虚拟域名
    ErrorLog "/private/var/log/apache2/dummy-host2.example.com-error_log"  //错误日志输出
    CustomLog "/private/var/log/apache2/dummy-host2.example.com-access_log" common
    <Directory "/usr/docs/dummy-host2.example.com"> //站点根目录   文件权限相关
                Options Indexes FollowSymLinks MultiViews
                AllowOverride None
                Require all granted
    </Directory>
</VirtualHost>

将注释中的配置项改好之后,wq保存退出。

3、修改host配置

sudo vim /etc/hosts

在文件末尾加上一行

127.0.0.1       dummy-host2.example.com  //刚配好的虚拟站点目录

保存退出。

4、将网站文件存放至虚拟主机目录,并重启服务器

sudo apachectl restart

三、调整迁移环境解决迁移问题

1、数据库密码匹配问题

为了以后的方便测试统一,将本地数据库密码与网站统一。

#### 输入密码,进入 mysql 数据库
mysql -u root -p

### 修改数据库密码
SET PASSWORD FOR 'root'@'localhost' = PASSWORD('newpass');

2、wordpress 中的 wp-config.php问题

(1)Apache 只能 解析 127.0.0.1  而 Nginx 可以解析 127.0.0.1 和 localhost  ,一开始这个地方填了 localhsot,导致 Apache 无法解析。

/** MySQL hostname  正确填法如下 **/

define('DB_HOST', '127.0.0.1');

(2)开启 debug 输出模式,方便查找 错误

define('WP_DEBUG', true);

(3)服务器内部错误,debug输出下发现文件夹权限问题,无法写入缓存

### 修改网站文件的权限,对指定 文件夹 文件 读写 全开放
sudo chmod -R 777 文件夹  (递归调用,使子文件 文件夹也是这个权限) 

### 对指定 文件夹 ,设置 文件 文件夹 用户组 和用户 ;就是文件所有者
sudo  chown  –R  apache:apache  文件夹  (递归调用,使子文件 文件夹也是这个权限) 

### 最后 设置好 文件权限
sudo  chmod   –R  755 文件夹  (递归调用,使子文件 文件夹也是这个权限) 
### 检查效果效果 ,可以查看文件 权限 用户组
ls  –l   文件夹 

### 移植成功后,别忘记 关闭 debug 模式:
define('WP_DEBUG', false);

3、url路径请求问题

当完成上面的操作时,我们发现 浏览器 可以访问 网站首页了,但是无法访问网站的其他子页面。都是显示  404  错误。经过查找,是路径 重写问题。

以前是nginx 服务器 现在改为 Apache ,所以 在  Permalink Settings 中,需要重新再 保存一下即可。

这样子:网站根目录下的   .htaccess 文件会添加 路径重写模块,开启路径重写功能。

# BEGIN WordPress
<IfModule mod_rewrite.c>
RewriteEngine On
RewriteBase /
RewriteRule ^index\.php$ - [L]
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule . /index.php [L]
</IfModule>

# END WordPress

建议删除 以下原网站的 配置文件,网站运行时 会根据新服务器环境  提示重新配置。
wordfence-waf.php 【插件生成的安全模块】
.htaccess 文件 【Apache 服务器下的 网站配置文件】
.user.ini 文件 【不清楚 之前哪里来的,反正也是在里面 只调用了 wordfence-waf.php 文件】

nginx.conf 【这个是nginx 服务器下的 网站配置文件,我这边是一个 缓存插件 自动生成的】

总结参考:

解决wordpress能打开(无404),但帖子无法打开的办法是:
在固定链接本身设置为 /%postname%/,即文章名的前提下:

1、确保apache的mod_rewrite是开启了。
即httpd.conf中,取消注释,已变成:

LoadModule rewrite_module libexec/apache2/mod_rewrite.so

2、确保有权限可以重写url,即httpd.conf中的<Directory />的AllowOverride从None变成了All:

<Directory /> 
Options FollowSymLinks 
AllowOverride All 
Order deny,allow 
Deny from all 
</Directory>

备份网站是虚拟主机配置,重写权限应该是下面的 虚拟主机 配置目录的AllowOverride All

<VirtualHost *:80>
        ServerAdmin [email protected]
        DocumentRoot "/Users/cool/Sites/www.yyyyy.com"
        ServerName www.yyyyy.com
        ErrorLog "/private/var/log/apache2/testLocalSite-error_log"
        CustomLog "/private/var/log/apache2/testLocalSite-access_log" common

        <Directory "/Users/cool/Sites/www.yyyyy.com">
            AllowOverride All
            Require all granted
        </Directory>
</VirtualHost>

 

四、网站域名更改

UPDATE wp_options SET option_value = replace( option_value, 'http://www.aliyun.com', 'http://www.bieryun.com' ) WHERE option_name = 'home' OR option_name = 'siteurl';
UPDATE wp_posts SET post_content = replace( post_content, 'http://www.aliyun.com', 'http://www.bieryun.com' ) ;
UPDATE wp_posts SET guid = replace( guid, 'http://www.old.com', 'http://www.new.com' ) ;

### old代表旧域名、new代表新域名,将上面的执行语句修改为自己的,然后点击执行即可!

 

 


参考:

https://www.jianshu.com/p/d09bdabbe065

 

springmvc基础-springmvc与struts2的比较

①. Spring MVC 的入口是 Servlet, 而 Struts2 是 Filter •
②. Spring MVC 会稍微比 Struts2 快些. Spring MVC 是基 •
于方法设计, 而 Sturts2 是基于类, 每次发一次请求都会实
例一个 Action.
③. Spring MVC 使用更加简洁, 开发效率Spring MVC确实 •
比 struts2 高: 支持 JSR303, 处理 ajax 的请求更方便
④. Struts2 的 • OGNL 表达式使页面的开发效率相比
Spring MVC 更高些.

spring踩坑-java.lang.NoClassDefFoundError: org/jboss/logging/BasicLogger

在spring 开发中,一开始 我遇到的 是 hibernate 类冲突,查了一下说,删掉就行了。

删掉后,果然就好了,第二次,我想将错误复现时,发现找不到了,并且出现新错误:

Caused by: java.lang.NoClassDefFoundError: org/jboss/logging/BasicLogger
	at java.lang.ClassLoader.defineClass1(Native Method)
	at java.lang.ClassLoader.defineClass(ClassLoader.java:763)
	at java.security.SecureClassLoader.defineClass(SecureClassLoader.java:142)
	at org.apache.catalina.loader.WebappClassLoaderBase.findClassInternal(WebappClassLoaderBase.java:2541)
	at org.apache.catalina.loader.WebappClassLoaderBase.findClass(WebappClassLoaderBase.java:858)
	at org.apache.catalina.loader.WebappClassLoaderBase.loadClass(WebappClassLoaderBase.java:1301)
	at org.apache.catalina.loader.WebappClassLoaderBase.loadClass(WebappClassLoaderBase.java:1166)
	at org.hibernate.validator.internal.util.logging.LoggerFactory.make(LoggerFactory.java:29)
	at org.hibernate.validator.internal.util.Version.<clinit>(Version.java:27)
	at org.hibernate.validator.internal.engine.ConfigurationImpl.<clinit>(ConfigurationImpl.java:65)
	at org.hibernate.validator.HibernateValidator.createGenericConfiguration(HibernateValidator.java:41)
	at javax.validation.Validation$GenericBootstrapImpl.configure(Validation.java:276)
	at org.springframework.validation.beanvalidation.LocalValidatorFactoryBean.afterPropertiesSet(LocalValidatorFactoryBean.java:250)
	at org.springframework.validation.beanvalidation.OptionalValidatorFactoryBean.afterPropertiesSet(OptionalValidatorFactoryBean.java:40)
	at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.invokeInitMethods(AbstractAutowireCapableBeanFactory.java:1687)
	at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.initializeBean(AbstractAutowireCapableBeanFactory.java:1624)
	... 28 more

发现是缺少了  jboss-logging的jar包,添加了就行。

于是我添加了 jboss-logging.jar  下载链接
因为org/jboss/logging/BasicLogger is inside jboss-logging.jar

springmvc基础-springmvc与spring的整合与关系

<!--  
	需要进行 Spring 整合 SpringMVC 吗 ?
	还是否需要再加入 Spring 的 IOC 容器 ?
	是否需要再 web.xml 文件中配置启动 Spring IOC 容器的 ContextLoaderListener ?
		
	1. 需要: 通常情况下, 类似于数据源, 事务, 整合其他框架都是放在 Spring 的配置文件中(而不是放在 SpringMVC 的配置文件中).
	实际上放入 Spring 配置文件对应的 IOC 容器中的还有 Service 和 Dao. 
	2. 不需要: 都放在 SpringMVC 的配置文件中. 也可以分多个 Spring 的配置文件, 然后使用 import 节点导入其他的配置文件
-->
	
<!--  
	问题: 若 Spring 的 IOC 容器和 SpringMVC 的 IOC 容器扫描的包有重合的部分, 就会导致有的 bean 会被创建 2 次.
	解决:
	1. 使 Spring 的 IOC 容器扫描的包和 SpringMVC 的 IOC 容器扫描的包没有重合的部分. 
	2. 使用 exclude-filter 和 include-filter 子节点来规定只能扫描的注解
-->
	
<!--  
	SpringMVC 的 IOC 容器中的 bean 可以来引用 Spring IOC 容器中的 bean. 
	返回来呢 ? 反之则不行. Spring IOC 容器中的 bean 却不能来引用 SpringMVC IOC 容器中的 bean!
-->

一、springmvc与spring的整合举例:

1、配置 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">
  
  	<!-- 配置启动 Spring IOC 容器的 Listener -->
	<!-- needed for ContextLoaderListener -->
	<context-param>
		<param-name>contextConfigLocation</param-name>
		<param-value>classpath:springioc.xml</param-value>
	</context-param>

	<!-- Bootstraps the root web application context before servlet initialization -->
	<listener>
		<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
	</listener>
  
  
  <servlet>
    <servlet-name>dispatcherServlet</servlet-name>
    <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
    <init-param>
      <param-name>contextConfigLocation</param-name>
      <param-value>classpath:springmvc.xml</param-value>
    </init-param>
    <load-on-startup>1</load-on-startup>
  </servlet>
  <servlet-mapping>
    <servlet-name>dispatcherServlet</servlet-name>
    <url-pattern>/</url-pattern>
  </servlet-mapping>

</web-app>

2、配置父容器 springioc.xml

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xmlns:context="http://www.springframework.org/schema/context"
	xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
		http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.0.xsd">

    <context:component-scan base-package="com.test.springmvc">
		<context:exclude-filter type="annotation" 
			expression="org.springframework.stereotype.Controller"/>
		<context:exclude-filter type="annotation" 
			expression="org.springframework.web.bind.annotation.ControllerAdvice"/>
	</context:component-scan>

	<!-- 配置数据源, 整合其他框架, 事务等. -->

</beans>

3、配置springmvc.xml

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xmlns:context="http://www.springframework.org/schema/context"
	xmlns:mvc="http://www.springframework.org/schema/mvc"
	xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
		http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.0.xsd
		http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc-4.0.xsd">

	<!-- 配置自动扫描的包 -->
	<context:component-scan base-package="com.test.springmvc" use-default-filters="false">
		<context:include-filter type="annotation" 
			expression="org.springframework.stereotype.Controller"/>
		<context:include-filter type="annotation" 
			expression="org.springframework.web.bind.annotation.ControllerAdvice"/>
	</context:component-scan>
	
	<!-- 配置视图解析器 -->
	<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
		<property name="prefix" value="/WEB-INF/views/"></property>
		<property name="suffix" value=".jsp"></property>
	</bean>


</beans>

4、编辑服务器:

package com.test.springmvc.handlers;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

@Service
public class UserService {

//	//因为父容器 配置文件 规定了 不扫描handler 所以,helloWorld无法注入
//	@Autowired
//	private HelloWorld helloWorld;
	
	public UserService() {
		System.out.println("UserService Constructor...");
	}
	
}

5、编辑控制器:

package com.test.springmvc.handlers;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;

@Controller // spring里面 又名handler 处理器
public class HelloWorld {

	/**
	 * 1、使用RequestMapping 注解来映射 请求的url
	 * 2、返回值会通过视图解析器解析为实际的物理视图,
	 * 对于org.springframework.web.servlet.view.InternalResourceViewResolver
	 * 会做如下解析,prefix + returnVal + suffix 得到实际的物理视图。然后做转发操作。
	 * 即 WEB-INF/views/success.jsp
 	 */

	@Autowired
	private UserService userService;
	
	public HelloWorld() {
		System.out.println("HelloWorld Constructor...");
	}
	
	@RequestMapping("/helloworld")
	public String hello( HttpServletRequest request,HttpServletResponse response){
		System.out.println("hello request" + request.toString());
		System.out.println("hello response" + response.toString());
		
		System.out.println("success");
		System.out.println(userService);
		
		return "success";
		//return "/WEB-INF/views/success.jsp";
     }
	
	
}

二、springmvc与spring容器关系

spring容器是父容器,springmvc是子容器。

子容器(相当于局部变量)可以访问父容器的对象(相当于全局变量)
但是父容器不能访问子容器中的对象。

springmvc基础-运行流程图

备注:

1、
<mvc:default-servlet-handler/>可以解决 请求静态资源问题。<mvc:annotation-driven></mvc:annotation-driven>可以保证非静态资源也能请求,当配置了<mvc:default-servlet-handler/>

2、在DispatcherServlet.doDispatch(request, response)中有一个 mappedHandler= getHandler(processedRequest);

mappedHandler 就是 HandlerExecutionChain (里面包含了控制器方法handler和拦截器Interceptors)

HandlerExecutionChain 由 handlerMapping 根据 处理器 和请求映射而来。

当没有配置1中的<mvc:default-servlet-handler/>和<mvc:annotation-driven>时,handlerMapping 包括:【
BeanNameUrlHandlerMapping
DefaultAnnotationHandlerMapping

当有配置1中时:handlerMapping 包括:【
RequestMappingHandlerMapping
SimpleUrlHandlerMapping
BeanNameUrlHandlerMapping

3、HandlerAdapter 里面有 MessageConverter

 

springmvc中级-异常处理

一、先构建一个普通程序

1、测试页面:

<a href="testExceptionHandlerExceptionResolver?i=10">测试异常</a>

2、编辑控制器:

@RequestMapping("/testExceptionHandlerExceptionResolver")
public String testExceptionHandlerExceptionResolver(@RequestParam("i") int i){
	System.out.println("result: " + (10 / i));
	return "success";
}

3、结果成功页面,就显示成功两字,很简单就不写了

二、编辑异常捕获

1、编辑捕获异常的代码

//这个是 之前已经写好的 控制器代码
@RequestMapping("/testExceptionHandlerExceptionResolver")
public String testExceptionHandlerExceptionResolver(@RequestParam("i") int i){
	System.out.println("result: " + (10 / i));
	return "success";
}

/**
 * 1. 在 @ExceptionHandler 方法的入参中可以加入 Exception 类型的参数, 该参数即对应发生的异常对象
 * 2. @ExceptionHandler 方法的入参中不能传入 Map. 若希望把异常信息传导页面上, 需要使用 ModelAndView 作为返回值
 * 3. @ExceptionHandler 方法标记的异常有优先级的问题. 
 * 4. @ControllerAdvice: 如果在当前 Handler 中找不到 @ExceptionHandler 方法来出来当前方法出现的异常, 
 * 则将去 @ControllerAdvice 标记的类中查找 @ExceptionHandler 标记的方法来处理异常. 
 */
@ExceptionHandler({ArithmeticException.class})
public ModelAndView handleArithmeticException(Exception ex){
	System.out.println("@ExceptionHandler+ArithmeticException.class+出异常了: " + ex);
	ModelAndView mv = new ModelAndView("error");
	mv.addObject("exception", ex);
	return mv;
}
	
@ExceptionHandler({RuntimeException.class})
public ModelAndView handleArithmeticException2(Exception ex){
	System.out.println("@ExceptionHandler+RuntimeException.class+ [出异常了]: " + ex);
	ModelAndView mv = new ModelAndView("error");
	mv.addObject("exception", ex);
	return mv;
}

注意:上面两个异常捕获器,精度不一样,实际使用时,抛出的异常按精度来捕获。【还有,异常捕获器中不能在方法中添加map 形参,这和普通的控制器不同,所以保存信息,必须先新建ModelAndView,然后保存并返回】

上面两个异常捕获器,作用域在这个控制器的类文件中,如果想要作用于所有的控制器类文件的话,需要新建一个异常捕获器类。

//因为是注解,所有这个全局异常捕获器,需要放在 注解扫描器 能够扫描到的目录
//在转发配置中,我们可以看到 <!-- 配置自动扫描的包 -->
//<context:component-scan base-package="com.test.springmvc"></context:component-scan>


@ControllerAdvice
public class SpringMVCTestExceptionHandler {

	@ExceptionHandler({RuntimeException.class})
	public ModelAndView handleArithmeticException(Exception ex){
		System.out.println("全局+@ControllerAdvice+@ExceptionHandler+ArithmeticException.class+----> 出异常了: " + ex);
		ModelAndView mv = new ModelAndView("error");
		mv.addObject("exception", ex);
		return mv;
	}
	
}

2、返回结果失败页面,就显示失败两字,很简单就不写了。

核心注意:上面提到的属于同一层的异常捕获,出现异常时,只能先内部捕获后全局捕获,且按照精确度优先原则。这一层的异常捕获,只要有一个捕获了,该层的其他捕获器就不捕获了。

三、产生异常测试

除数为0,抛出异常。

<a href="testExceptionHandlerExceptionResolver?i=0">测试异常</a>

四、@ResponseStatus注解

直接在异常页,显示编辑过的响应头信息。
1、可以注解一个类或一个方法,当注解为一个类时,该异常作用于所有控制器。

@ResponseStatus(value=HttpStatus.FORBIDDEN, reason="用户名和密码不匹配!")
public class UserNameNotMatchPasswordException extends RuntimeException{

	/**
	 * 
	 */
	private static final long serialVersionUID = 1L;

	public  UserNameNotMatchPasswordException() {
		System.out.println("全局+@ResponseStatus+RuntimeException+ArithmeticException.class+----> 出异常了: ");
	}
	
}

2、也可以直接注解在控制器方法上。此时,控制器无论是否抛出异常,都会按照设置的异常信息,展示异常网页。(也就是无论是否有异常,控制器自己都会捕获异常,并展示@ResponseStatus设置好的异常信息)

@ResponseStatus(reason="测试",value=HttpStatus.NOT_FOUND)
@RequestMapping("/testResponseStatusExceptionResolver")
public String testResponseStatusExceptionResolver(@RequestParam("i") int i){
	if(i == 13){
		System.out.println("throw +testResponseStatusExceptionResolver...");
		throw new UserNameNotMatchPasswordException();
	}
	System.out.println("testResponseStatusExceptionResolver...");
		
	return "success";
}

注意:@ResponseStatus注解

1、如果控制器没有抛出异常,也不会跳转到success页面,而是用tomcat默认的异常页面,例如:  HTTP Status 404 – 测试

2、如果出现异常,且有@ExceptionHandler, @ControllerAdvice  异常捕获类,那么异常会被二次捕获了。【先ResponseStatus捕获,然后ExceptionHandler又捕获,这两种捕获器属于两个层次】

[FirstIntercept] preHandle
throw +testResponseStatusExceptionResolver...
全局+@ResponseStatus+RuntimeException+ArithmeticException.class+----> 出异常了: 
@ExceptionHandler+RuntimeException.class+ [出异常了]: com.test.springmvc.test.UserNameNotMatchPasswordException
[FirstIntercept] afterCompletion

3、如果出现异常,且没有@ExceptionHandler, @ControllerAdvice  异常捕获类,那么就由ResponseStatus 捕获一次就结束了。

五、DefaultHandlerExceptionResolver

这个就是springmvc默认的异常处理,比如我们控制器编写错误,出异常了,如果我们写异常处理模块,那么默认情况下就是这个类处理的。比如控制器只支持post请求,然后用get请求就报错,请求方法不支持。

目前测试到的异常情况看,默认异常处理器,捕获到异常后,不会将异常传递给其他异常捕获器了。

直接在网页中显示错误:

HTTP Status 405 - Request method 'GET' not supported

type Status report

message Request method 'GET' not supported

description The specified HTTP method is not allowed for the requested resource.

Apache Tomcat/8.0.36

控制台有错误信息:

org.springframework.web.servlet.PageNotFound handleHttpRequestMethodNotSupported
WARNING: Request method 'GET' not supported

六、SimpleMappingExceptionResolveer

在控制器中:

@RequestMapping("/testSimpleMappingExceptionResolver")
public String testSimpleMappingExceptionResolver(@RequestParam("i") int i){
	String [] vals = new String[10];
	System.out.println(vals[i]);
	return "success";
}

测试网页:

http://localhost:8080/springmvc-2/testSimpleMappingExceptionResolver?i=10

编辑转发配置器:

<!-- 配置使用 SimpleMappingExceptionResolver 来映射异常 -->
<bean class="org.springframework.web.servlet.handler.SimpleMappingExceptionResolver">
	<!-- 配置请求域 异常属性名 ex,默认值为 exception -->
	<property name="exceptionAttribute" value="ex"></property>
	<!-- 异常类型 映射 跳转页  -->
	<property name="exceptionMappings">
		<props>
			<prop key="java.lang.ArrayIndexOutOfBoundsException">error</prop>
		</props>
	</property>
</bean>

七、异常处理器的优先级

同一个异常:

DefaultHandlerExceptionResolver (优先级第一,捕获到异常后,就将异常截断,在网页中展示异常信息,不会再往下传递)

@ResponseStatus(优先级第二,捕获到异常后,会将异常往后传递,返回结果以最后一个异常返回网页信息为准)

在控制器中,添加这个@ResponseStatus注解,就算没有抛出异常,也会展示指定状态的异常的网页。

@ResponseStatus(reason="测试",value=HttpStatus.NOT_FOUND)
@RequestMapping("/testResponseStatusExceptionResolver")
public String testResponseStatusExceptionResolver(@RequestParam("i") int i){
	if(i == 13){
		System.out.println("throw +testResponseStatusExceptionResolver...");
		throw new UserNameNotMatchPasswordException();
	}
	System.out.println("testResponseStatusExceptionResolver...");
		
	return "success";
}

如果上面的控制器没有@ResponseStatus(reason="测试",value=HttpStatus.NOT_FOUND) ,然后异常时抛出了UserNameNotMatchPasswordException类

对UserNameNotMatchPasswordException查看编码信息:

@ResponseStatus(value=HttpStatus.FORBIDDEN, reason="用户名和密码不匹配!")
public class UserNameNotMatchPasswordException extends RuntimeException{

	/**
	 * 
	 */
	private static final long serialVersionUID = 1L;
	
	public  UserNameNotMatchPasswordException() {
		System.out.println("全局+@ResponseStatus+RuntimeException+ArithmeticException.class+----> 出异常了: ");
	}
	
}

看上面的异常类,如果项目中没有低优先级的异常捕获器,那么网页就返回错误信息就结束了。如果有其他低优先级的异常了,那么异常还会被继续处理。

@ExceptionHandler和@ControllerAdvice(优先级第三,异常被捕获后,不会继续往下传递了)

@ExceptionHandler适用于和控制器位于同一个文件中,@ControllerAdvice单独一个文件,作用域针对所有的控制器。

SimpleMappingExceptionResolver(异常映射器,优先级最低,针对异常的类型,指定错误和异常的返回页)

<!-- 配置使用 SimpleMappingExceptionResolver 来映射异常 -->
<bean class="org.springframework.web.servlet.handler.SimpleMappingExceptionResolver">
	<!-- 配置请求域 异常属性名 ex,默认值为 exception -->
	<property name="exceptionAttribute" value="ex"></property>
	<!-- 异常类型 映射 跳转页  -->
	<property name="exceptionMappings">
		<props>
			<prop key="java.lang.ArrayIndexOutOfBoundsException">error</prop>
		</props>
	</property>
</bean>

补充:

针对指定的异常信息返回页,可以在返回页的网页中,直接读取异常信息。
默认jstl 表达式为:  ${requestScope.exception},在SimpleMappingExceptionResolver 中信息配置时,将异常的属性名改成了ex,所以错误信息展示页 该调用 ${requestScope.ex}来获取异常信息。

springmvc中级-自定义拦截器

Spring MVC也可以使用拦截器对请求进行拦截处理,用户 可以自定义拦截器来实现特定的功能,自定义的拦截器必 须实现HandlerInterceptor接口。

preHandle():这个方法在业务处理器处理请求之前被调用,在该 方法中对用户请求 request 进行处理。如果程序员决定该拦截器对 请求进行拦截处理后还要调用其他的拦截器,或者是业务处理器去 进行处理,则返回true;如果程序员决定不需要再调用其他的组件 去处理请求,则返回false。

postHandle():这个方法在业务处理器处理完请求后,但是DispatcherServlet 向客户端返回响应前被调用,在该方法中对 用户请求request进行处理。

afterCompletion():这个方法在 DispatcherServlet 完全处理完请 求后被调用,可以在该方法中进行一些资源清理的操作。


自定义拦截器大白话:

1、首先新建一个拦截器类

就是 implements HandlerInterceptor 实现接口类就行。

public class FirstIntercept implements HandlerInterceptor{

	/**
	 * 该方法在目标方法之前被调用.
	 * 若返回值为 true, 则继续调用后续的拦截器和目标方法. 
	 * 若返回值为 false, 则不会再调用后续的拦截器和目标方法. 
	 * 
	 * 可以考虑做权限. 日志, 事务等. 
	 */
	@Override
	public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
			throws Exception {
		// TODO Auto-generated method stub
		System.out.println("[FirstIntercept] preHandle");
		return true;
	}


	/**
	 * 调用目标方法之后, 但渲染视图之前. 
	 * 可以对请求域中的属性或视图做出修改. 
	 */
	@Override
	public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler,
			ModelAndView modelAndView) throws Exception {
		// TODO Auto-generated method stub
		System.out.println("[FirstIntercept] postHandle");

		
	}


	/**
	 * 渲染视图之后被调用. 释放资源
	 */
	@Override
	public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex)
			throws Exception {
		// TODO Auto-generated method stub
		System.out.println("[FirstIntercept] afterCompletion");

	}

}

2、编辑转发配置文件

<mvc:interceptors>
       
        <!-- 配置自定义拦截器 -->
	<bean class="com.test.springmvc.test.FirstIntercept"></bean>
	
	<!-- 配置 带参数的 自定义拦截器,SencondIntercept和FirstIntercept 代码一样的只是改了名字,这里就不写了 -->
	<mvc:interceptor>
            <!-- 拦截器只作用在 /emps 请求域中,一定要记得“/”,否则没有效果 -->
	    <mvc:mapping path="/emps"/>
	    <bean class="com.test.springmvc.test.SecondIntercept"></bean>
	</mvc:interceptor>
	
</mvc:interceptors>

3、拦截器执行顺序:

(1)如果只配置了FirstIntercept,且里面的 preHandle方法返回true,且没有  配置 带参数的 自定义拦截器(即SencondIntercept
那么url请求,控制台输出结果:

[FirstIntercept] preHandle
[FirstIntercept] postHandle
[FirstIntercept] afterCompletion

(2)如果上面的FirstIntercept里面的preHandle方法返回false。且没有  配置 带参数的 自定义拦截器(即SencondIntercept
那么url请求,控制台输出结果:

[FirstIntercept] preHandle

(3)如果配置了FirstInterceptSecondIntercept,且两者里面的preHandle方法都返回true,且请求域是emps
控制台输出结果:

[FirstIntercept] preHandle
[SecondIntercept] preHandle
[SecondIntercept] postHandle
[FirstIntercept] postHandle
[SecondIntercept] afterCompletion
[FirstIntercept] afterCompletion

(4)如果配置了FirstInterceptSecondIntercept请求域是emps
FirstIntercept的preHandle方法返回true,SecondIntercept的preHandle方法返回false,控制台输出结果:

[FirstIntercept] preHandle
[SecondIntercept] preHandle
[FirstIntercept] afterCompletion

备注:下图中的虚线不是执行过程实线才是真正的执行过程

 

特别提醒:当初在改项目代码时,因为代码是热更新的,但是测试结果来看,热更新后,Intercept 的测试输出 有bug ,因为多输出了 一遍 下面的:
[FirstIntercept] preHandle
[FirstIntercept] postHandle
[FirstIntercept] afterCompletion

所以,测试代码时最好还是重启一下服务器,这样就不会出现意想不到的bug。

4、拦截器顺序与异常

当自定义拦截器的preHandle方法返回true,遇到springmvc出现异常时:

1、DefaultHandlerExceptionResolver  系统默认自带异常处理器。
里面实现了很多异常 ,如:handleHttpRequestMethodNotSupported

我测试控制器规定了请求方法只能是post,当我get请求时,出异常了。

@RequestMapping(value="/testDefaultHandlerExceptionResolver",method=RequestMethod.POST)
public String testDefaultHandlerExceptionResolver(){
	System.out.println("testDefaultHandlerExceptionResolver...");
	return "success";
}

控制台打印的是:(看出来,自定义拦截器根本没有调用到)

org.springframework.web.servlet.PageNotFound handleHttpRequestMethodNotSupported
WARNING: Request method 'GET' not supported

网页显示:

HTTP Status 405 - Request method 'GET' not supported

type Status report

message Request method 'GET' not supported

description The specified HTTP method is not allowed for the requested resource.

Apache Tomcat/8.0.36

2、其他 遇到下面的异常捕获器:

@ResponseStatus (优先级第一)

@ExceptionHandler,@ControllerAdvice(优先级第二)

SimpleMappingExceptionResolver (优先级第三)

控制台结果显示:(自定义拦截器调用了方法一和方法三)

[FirstIntercept] preHandle
[FirstIntercept] afterCompletion

 

springmvc中级-文件上传与下载

Spring MVC 为文件上传提供了直接的支持,这种支持是通 过即插即用的 MultipartResolver 实现的。Spring 用 Jakarta Commons FileUpload 技术实现了一个MultipartResolver 实现类:CommonsMultipartResovler

Spring MVC 上下文中默认没有装配 MultipartResovler,因 此默认情况下不能处理文件的上传工作,如果想使用 Spring 的文件上传功能,需现在上下文中配置 MultipartResolver

defaultEncoding: 必须和用户 JSP 的 pageEncoding 属性 一致,以便正确解析表单的内容。

为了让 CommonsMultipartResovler 正确工作,必须先 将 Jakarta Commons FileUpload 及 Jakarta Commons io 的类包添加到类路径下。

文件上传大白话:

1、添加文件上传的jar包

commons-fileupload-1.2.1.jar
commons-io-2.0.jar

2、编辑转发配置文件

<!-- 文件上传 配置  multipartresolver -->
<bean id="multipartResolver" 
class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
<property name="defaultEncoding" value="utf-8"></property>
<property name="maxUploadSize" value="102400"></property>
</bean>

3、网页测试页:

<form action="testFileUpload" method="post" enctype="multipart/form-data">
File:<input type="file" name="name-file"/>
Desc:<input type="text" name="name-desc"/>
<input type="submit" name="name-submit" value="submit" />
</form>

4、编辑控制器controler

@RequestMapping("/testFileUpload")
public String testFileUpload(@RequestParam("name-desc") String desc, 
		@RequestParam("name-file") MultipartFile file) throws IOException{
	System.out.println("name-desc: " + desc);
	System.out.println("OriginalFilename: " + file.getOriginalFilename());
	System.out.println("InputStream: " + file.getInputStream());
	return "success";
}

 

文件下载:

已经在HttpMessageConverter详解中的,ResponseEntity<byte[]>已经提到过了。

@RequestMapping("/testResponseEntity")
public ResponseEntity<byte[]> testResponseEntity(HttpSession session) throws IOException{
	byte [] body = null;
	ServletContext servletContext = session.getServletContext();
	InputStream in = servletContext.getResourceAsStream("/files/abc.txt");
	body = new byte[in.available()];
	in.read(body);
		
	HttpHeaders headers = new HttpHeaders();
	headers.add("Content-Disposition", "attachment;filename=abc.txt");
		
	HttpStatus statusCode = HttpStatus.OK;
		
	ResponseEntity<byte[]> response = new ResponseEntity<byte[]>(body, headers, statusCode);
	return response;
}