1.Spring Bean的作用域之间有什么区别
文字说明
bean的作用域
可以通过scope属性来指定bean的作用域
-
-singleton:默认值。当IOC容器——创建就会创建bean的实例,而且是单例的,每次得到的都是同一个
- 在SpringlOC容器中仅存在一个Bean实例,Bean以单实例的方式存在
-
-prototype:原型的。当IOC容器——创建不再实例化该bean,而且每调用一次创建一个对象
- 每次调用getBean方法时再实例化该bean,每次调用getBean()时都会返回一个新的实例
-
-request:每次请求实例化一个bean
-
每次HTTP请求都会创建一个新的 Bean,该作用域仅适用于WebApplicationContext环境
-
必须是 web环境
-
-
-session:在一次会话中共享一个bean
- 同一个HTTP Session共享一个Bean,不同的HTTP Session使用不同的 Bean。该作用域仅适用于WebApplicationContext环境
-
globalsession:每个全局的HTTP Session,使用session定义的Bean都将产生一个新实例。典型情况下,仅在使用portlet context的时候有效。同样只有在Web应用中使用Spring时,该作用域才有效
在Spring 中,可以在元素的scope属性里设置bean的作用域,以决定这个bean是单实例的还是多实例的。
默认情况下, Sdring 只为每个在IOC容器里声明的bean 创建唯一一个实例,整个IOC容器范围内都能共享该实例:所有后续的getBean()调用和 bean引用都将返回这个唯一的bean 实例。该作用域被称为singleton,它是所有bean的默认作用域。
项目搭建
book类
public class Book {
private Integer id;
private String title;
private String author;
private double price;
private Integer sales;
public Book() {
System.out.println("Book对象被创建了");
}
public Book(Integer id, String title, String author, double price, Integer sales) {
super();
this.id = id;
this.title = title;
this.author = author;
this.price = price;
this.sales = sales;
System.out.println("全参的构造器被调用");
}
public Book(Integer id, String title, String author, double price) {
super();
this.id = id;
this.title = title;
this.author = author;
this.price = price;
System.out.println("不含销量的构造器被调用");
}
public Book(Integer id, String title, String author, Integer sales) {
super();
this.id = id;
this.title = title;
this.author = author;
this.sales = sales;
System.out.println("不含价格的构造器被调用");
}
//get set toString
}
测试类
class SpringTest {
//01.Spring Bean的作用域之间有什么区别
//创建IOC容器对象
ApplicationContext ioc = new ClassPathXmlApplicationContext("beans.xml");
@Test
void testBook() {
Book book = (Book) ioc.getBean("book");
Book book2 = (Book) ioc.getBean("book");
System.out.println(book==book2);
}
}
xml类
- beans.xml,在 config目录下,编译后:变成 bin下
- config 和 src 都是源文件夹,相当于在 src 下
- bin 是排除的
<?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">
<bean id="book" class="com.atguigu.spring.beans.Book" scope="prototype">
<property name="id" value="8"></property>
<property name="title" value="红高粱"></property>
<property name="author" value="莫言"></property>
<property name="price" value="10.00"></property>
<property name="sales" value="800"></property>
</bean>
</beans>
要引入的包
commons-logging-1.1.3.jar
spring-aop-4.0.0.RELEASE.jar
spring-beans-4.0.0.RELEASE.jar
spring-context-4.0.0.RELEASE.jar
spring-core-4.0.0.RELEASE.jar
spring-expression-4.0.0.RELEASE.jar
2. 事务传播属性和隔离级别
- 请简单介绍Spring支持的常用数据库事务传播属性和事务隔离级别?
传播行为
当事务方法被另一个事务方法调用时,必须指定事务应该如何传播。例如:方法可能继续在现有事务中运行,也可能开启一个新事务,并在自己的事务中运行。
事务的传播行为可以由传播属性指定。Spring定义了7种类传播行为。
propagation
英
/ˌprɒpəˈɡeɪʃn/
n.
(动植物等的)繁殖,增殖,;(观点、理论等的)传播;(运动、光线、声音等的)传送
mandatory
英
/ˈmændətəri/
adj.
强制性的,义务的;受(前国际联盟)委任统治的
n.
受托人,代理人(=mandatary)
nested
英
/ˈnestɪd/
adj.
嵌套的,内装的
v.
筑巢;嵌入(nest 的过去分词)
- 2个不支持事务。
- 一个报错。
- 一个挂起。
- 5个支持事务。
- 有就用,没有就创建。
- 支持你,你有 我就有。
- 强制,你没有事务,就抛异常。
- 我创建新事务,把你的挂起。
- 我创建新事务,嵌套你的内部。
public enum Propagation {
REQUIRED(0), //required 需要的。有就用,没有就新建。
SUPPORTS(1), //supports 支持。支持你,你有,我就有。
MANDATORY(2), //强制 运行在事务内部,没有 就抛出异常。
REQUIRES_NEW(3),//require的第三人称单数形式。我创建新的事务,把你挂起。
NOT_SUPPORTED(4),//不支持事务。你有事务,把你的挂起。
NEVER(5),//从不,决不。有运行的事务、就抛出异常
NESTED(6);//嵌套。我启动一个新的,你有就和你嵌条。
}
//REQUIRED
//如果有事务在运行,当前的方法就在这个事务内运行,否则,就启动一个新的事务,并在自己的事务内运行
//REQUIRES_NEW
//当前的方法必须启动新事务,并在它自己的事务内运行.如果有事务正在运行,应该将它挂起
//SUPPORTS
//如果有事务在运行,当前的方法就在这个事务内运行.否则它可以不运行在事务中.
//NOT_SUPPORTED
//当前的方法不应该运行在事务中.如果有运行的事务,将它挂起
//MANDATORY
//当前的方法必须运行在事务内部,如果没有正在运行的事务,就抛出异常
//NEVER
//当前的方法不应该运行在事务中.如果有运行的事务、就抛出异常
//NESTED
//如果有事务在运行,当前的方法就应该在这个事务的嵌套事务内运行.否则,就启动一个新的事务,并在它自己的事务内运行.
//事务传播属性可以在@Transactional 注解的propagation属性中定义。
代码演示
去结账 接口和实现
- 去结账 开始了事务,调用
public interface Cashier {
/**
* 去结账
*
* @param userId
* @param isbns
*/
void checkout(int userId, List<String> isbns);
}
@Service("cashier")
public class CashierImpl implements Cashier {
@Autowired
private BookShopService bookShopService;
@Transactional //去结账开始了事务
@Override
public void checkout(int userId, List<String> isbns) {
for (String isbn : isbns) {
//调用BookShopService中买东西的方法
bookShopService.purchase(userId, isbn);
}
}
}
买东西的接口和实现
- 被结账调用,结账开始了事务,
- 我的买东西方法,也是开始事务的。
public interface BookShopService {
/**
* 买东西
*
* @param userId
* @param isbn
*/
void purchase(int userId, String isbn);
}
/**
* @Transactional注解
* 该注解可以添加到类上,也可以添加到方法上
* 如果添加到类上,那么类中所有的方法都添加上了事务
* 如果添加到方法上,只有添加了该注解的方法才添加了事务
*/
//@Transactional
@Service("bookShopService")
public class BookShopServiceImpl implements BookShopService {
@Autowired
private BookShopDao bookShopDao;
@Transactional(propagation=Propagation.REQUIRED,
isolation=Isolation.READ_COMMITTED)
@Override
public void purchase(int userId, String isbn) {
//1.获取要买的图书的价格
double bookPrice = bookShopDao.getBookPriceByIsbn(isbn);
// System.out.println(bookPrice);
//2.更新图书的库存
bookShopDao.updateBookStock(isbn);
//3.更新用户的余额
bookShopDao.updateAccountBalance(userId, bookPrice);
// double bookPriceByIsbn = bookShopDao.getBookPriceByIsbn(isbn);
// System.out.println(bookPriceByIsbn);
}
}
结果
/**
* 事务的属性:
* 1.★propagation:用来设置事务的传播行为
* 事务的传播行为:一个方法运行在了一个开启了事务的方法中时,当前方法是使用原来的事务还是开启一个新的事务
//A方法,运行在 B中(已开事务)。A使用B的事务?还是另开?
//purchase方法 是使用,自己的事务,还是使用 调用者 结账checkout 的事务?
*/
/*
* -Propagation.REQUIRED:默认值,使用原来的事务
* -Propagation.REQUIRES_NEW:将原来的事务挂起,开启一个新的事务
* 2.★isolation:用来设置事务的隔离级别
* -Isolation.REPEATABLE_READ:可重复读,MySQL默认的隔离级别
* -Isolation.READ_COMMITTED:读已提交,Oracle默认的隔离级别,开发时通常使用的隔离级别
*/
@Transactional(propagation=Propagation.REQUIRED,isolation=Isolation.READ_COMMITTED)
①REQUIRED传播行为
当bookService.的 purchase0方法被另一个事务方法 checkout(调用时,它默认会在现有的事务内运行。这个默认的传播行为就是REQUIRED。因此在checkoutQ方法的开始和终止边界内只有一个事务。这个事务只在checkoutQ方法结束的时候被提交,结果用户一本书都买不了。
②.REQUIRES_NEW传播行为
表示该方法必须启动一个新事务,并在自己的事务内运行。如果有事务在运行,就
应该先挂起它。
- 默认的隔离级别,有就用,没有就创建。所以:都会购买失败,事务会回滚。
- REQUIRES_NEW,开启一个新的事务,把你的挂起,可以购买成功一本书。
- 假如:checkout 事务为t1,purchase 事务为 t2,t3
- t1开始,调用purchase 后,t1挂起。
- t2开始,t2结束。t1可以继续了 在此调用 purchase 。
- t1 又被挂起,t3开始,t3结束,t1 又可以继续,t1结束。
- t3失败了,对 t2 事务 没有影响。
- t2开始,t2结束。t1可以继续了 在此调用 purchase 。
测试
class TxTest {
//创建IOC容器对象
ApplicationContext ioc = new ClassPathXmlApplicationContext("beans-tx.xml");
@Test
void testBookShopDao() {
//获取BookDao
BookShopDao bookShopDao = (BookShopDao) ioc.getBean("bookShopDao");
double bookPrice = bookShopDao.getBookPriceByIsbn("1001");
System.out.println(bookPrice);
//更新图书的库存
bookShopDao.updateBookStock("1001");
//更新账户的余额
bookShopDao.updateAccountBalance(1, bookPrice);
}
@Test
void testCashier() {
Cashier cashier = (Cashier) ioc.getBean("cashier");
//创建List
List<String> isbns = new ArrayList<>();
isbns.add("1001");//只有100块钱,只能买这本书,下一本买不起。
isbns.add("1002");
//去结账
cashier.checkout(1, isbns);
}
@Test //测试 打断点,读取两次结果。默认是 可重复读。
void testBookShopService() {
BookShopService bookShopService = (BookShopService) ioc.getBean("bookShopService");
bookShopService.purchase(1, "1001");
}
}
-
数据库 余额是 Unsigned,无符号的,只能为 整数,用于 金额,就是这样用的。
- 如果为负数,数据库会报 超出余额的范围。
项目其他无用的
- beans-tx.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:tx="http://www.springframework.org/schema/tx"
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/tx http://www.springframework.org/schema/tx/spring-tx-4.0.xsd">
<!-- 设置自动扫描的包 -->
<context:component-scan base-package="com.atguigu.spring.tx"></context:component-scan>
<!-- 引入外部的属性文件 -->
<context:property-placeholder location="classpath:druid_tx.properties" />
<!-- 配置数据源 -->
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
<property name="username" value="${jdbc.username}"></property>
<property name="password" value="${jdbc.password}"></property>
<property name="url" value="${jdbc.url}"></property>
<property name="driverClassName" value="${jdbc.driverClassName}"></property>
<property name="initialSize" value="${jdbc.initialSize}"></property>
<property name="minIdle" value="${jdbc.minIdle}"></property>
<property name="maxActive" value="${jdbc.maxActive}"></property>
<property name="maxWait" value="${jdbc.maxWait}"></property>
</bean>
<!-- 配置JdbcTemplate -->
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<!-- 配置数据源属性值 -->
<property name="dataSource" ref="dataSource"></property>
</bean>
<!-- 配置事务管理器 -->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<!-- 配置数据源属性值 -->
<property name="dataSource" ref="dataSource"></property>
</bean>
<!-- 开启事务注解支持
如果事务管理器的id属性值为transactionManager,那么transaction-manager属性可以省略不写
-->
<!-- <tx:annotation-driven transaction-manager="transactionManager"/> -->
<tx:annotation-driven/>
</beans>
- druid_tx.properties
jdbc.username=root
jdbc.password=root
jdbc.url=jdbc:mysql://localhost:3306/spring_transaction
jdbc.driverClassName=com.mysql.jdbc.Driver
jdbc.initialSize=10
jdbc.minIdle=5
jdbc.maxActive=20
jdbc.maxWait=5000
- BookShopDao
@Repository("bookShopDao")
public class BookShopDaoImpl implements BookShopDao {
@Autowired
private JdbcTemplate jdbcTemplate;
@Override
public double getBookPriceByIsbn(String isbn) {
// 写sql语句
String sql = "select price from book where isbn = ?";
// 调用JdbcTemplate中的queryForObject方法
Double bookPrice = jdbcTemplate.queryForObject(sql, Double.class, isbn);
return bookPrice;
}
@Override
public void updateBookStock(String isbn) {
// 写sql语句
String sql = "update book_stock set stock = stock - 1 where isbn = ?";
// 调用JdbcTemplate中的update方法
jdbcTemplate.update(sql, isbn);
}
@Override
public void updateAccountBalance(int userId, double bookPrice) {
// 写sql语句
String sql = "update account set balance = balance - ? where id = ?";
// 调用JdbcTemplate中的update方法
jdbcTemplate.update(sql, bookPrice, userId);
}
}
4个隔离级别3问题
隔离级别
isolation
英
/ˌaɪsəˈleɪʃn/
n.
隔离,孤立;孤独;绝缘;(尤指对混合物或微生物的)离析
public enum Isolation {
DEFAULT(-1),
READ_UNCOMMITTED(1), //读 未提交
READ_COMMITTED(2), //读 已提交
REPEATABLE_READ(4), //可重复度
SERIALIZABLE(8);//串行执行
}
隔离级别 | 脏读 | 不可重复读 | 幻读 | 加锁读 |
---|---|---|---|---|
读未提交(Read uncommitted) | √ | √ | √ | 不加锁 |
读已提交(Read committed) | X | √ | √ | 不加锁 |
可重复读(Repeatable read) | X | X | √ | 不加锁 |
可串行化(Serializable ) | X | X | X | 加锁 |
读未提交:
- 允许Transaction01读取Transaction02未提交的修改。
读已提交:
- 要求Transaction01只能读取Transaction02已提交的修改。
可重复读:
-
确保Transaction01可以多次从一个字段中读取到相同的值,即 Transaction01执行期间禁止其它事务对这个字段进行更新。
尚硅谷的这个应该错了,不会禁止。 -
即:我开事务的那一刻,就决定了 当前数据库在这个点了。随便查,都是这一刻的数据(不会被他人影响)
串行化:
- 确保Transaction01可以多次从一个表中读取到相同的行,在Transaction01执行期间,禁止其它事务对这个表进行添加、更新、删除操作。可以避免任何并发问题,但性能十分低下。
可重复读出现幻读
- 基本情况,t1 开启事务,t2 开启事务,t1 新增 并提交。 t2 事务中 不会查到最新的。
- 针对如果事务 1 中只有两次 select,事务 2 在事务 1 的两次 select 之间插入了影响 select 结果的数据,事务 1 的两次 select 结果是一样的。所以可重复读的隔离级别不是完全不能隔离幻读。
- https://blog.csdn.net/rsjssc/article/details/123465816
有两种情况无法防止幻读。
-
一是主动用 for share 或者 for update 这样带锁的 select 语句, 对应下文实验中的现象
- 事务1 开启事务,事务2 开启事务。
- 事务1 插入后提交。事务2,查询(查询不到最新的),但 for update 查询可以。
-
二是 开始较早的事务更新了 开始较晚但是结束较早 的事务的新数据,
- 那么开始较早的事务中会出现幻读,对应下文实验中的现象 2。
- t1 开事务,t2 开事务,插入数据 提交。
- t1 更新这条数据,t1 会更新成功(t2插入的这条)。t1提交事务。
-
MVCC多版本并发控制(Multi-Version Concurrency Control)是MySQL中基于乐观锁理论实现隔离级别的方式,用于实现读已提交和可重复读取隔离级别。
-
Oracle只支持,读已提交(默认)和 可串行化
-- mysql> SELECT @@tx_isolation; ,mysql默认 可重复读。
-- +-----------------+
-- | @@tx_isolation |
-- +-----------------+
-- | REPEATABLE-READ |
-- +-----------------+
SET SESSION TRANSACTION ISOLATION LEVEL Repeatable read
SAVEPOINT a
ROLLBACK TO b
start transaction;
ROLLBACK
commit; -- 提交事务后。
concurrency
英
/kənˈkʌrənsɪ/
n.
[计] 并发性;同时发生
3问题1
脏读(dirty read):当一个事务读取另一个事务尚未提交的改变时,产生脏读
不可重复读(nonrepeatable read):同一查询在同一事务中多次进行,由于其他提交事务所做的修改或删除,每次返回不同的结果集,此时发生不可重复读。
-
C2中看到了 C1的修改,C2就发生了 不可重复读。
-
C2在统计10:00之前的数据,却读到了 C2在10点以后 提交的数据。
-
主要是:修改和删除。
幻读(phantom read):同一查询在同一事务中多次进行,由于其他提交事务所做的插入操作,每次返回不同的结果集,此时发生幻读。
- 主要是:插入操作。
3问题2
假设现在有两个事务: Transaction01和 Transaction02并发执行。
- 脏读:当前事务,读取到了 其他事务更新,但没有提交的值。
- t1 将某条记录的AGE值从20修改为30。
- t2 读取了Transaction01更新后的值:30。
- t1 回滚,AGE值恢复到了20。
- t2 读取到的30就是一个无效的值。
2)不可重复读:当前事务,读第二次,读取到了 其他事务更新(已提交),读取两次不一致。
- t1 读取了AGE值为20。
- t2 将AGE值修改为30。
- t1 再次读取AGE值为30,和第一次读取不一致。
3)幻读:每次返回不同的结果集,多了数据(其他事务插入)
- 感觉出现了 幻觉。
- t1 读取了STUDENT表中的一部分数据。
- t2 向STUDENT 表中插入了新的行。
- t1 读取了STUDENT表时,多出了一些行。
课外:innodb 和 myisam
1、innodb支持事务,而myisam不支持事务。
2、innodb支持外键,而myisam不支持外键。
3、innodb默认表锁,使用索引检索条件时是行锁,而myisam是表锁(每次更新增加删除都会锁住表)。
4、innodb和myisam的索引都是基于b+树,但他们具体实现不一样,innodb的b+树的叶子节点是存放数据的,myisam的b+树的叶子节点是存放指针的。
5、innodb是聚簇索引,必须要有主键,一定会基于主键查询,但是辅助索引就会查询两次,myisam是非聚簇索引,索引和数据是分离的,索引里保存的是数据地址的指针,主键索引和辅助索引是分开的。
6、innodb不存储表的行数,所以select count( * )的时候会全表查询,而myisam会存放表的行数,select count(*)的时候会查的很快。
总结:mysql默认使用innodb,如果要用事务和外键就使用innodb,如果这张表只用来查询,可以用myisam。如果更新删除增加频繁就使用innodb。
原文链接:https://blog.csdn.net/weixin_41832850/article/details/106063426
-
MySQL 5.1之前的版本中,默认的搜索引擎是MyISAM,从MySQL 5.5之后的版本中,默认的搜索引擎变更为InnoDB
-
MyISAM:默认表类型,它是基于传统的ISAM类型,它是存储记录和文件的标准方法。不是事务安全的,而且不支持外键。
-
InnoDB:支持事务安全的引擎,支持外键、行锁、事务是他的最大特点。
03.SpringMVC中如何解决POST请求中文乱码问题
- GET的又如何处理呢
- 在 tomcat 7.0,server.xml下
- 第一个 <Connector 标签 URIEncoding = “UTF-8”
- 在 tomcat 7.0,server.xml下
CharacterEncodingFilter
public class CharacterEncodingFilter extends OncePerRequestFilter {
private String encoding;
private boolean forceEncoding = false;
public CharacterEncodingFilter() {
}
public void setEncoding(String encoding) {
this.encoding = encoding;
}
public void setForceEncoding(boolean forceEncoding) {
this.forceEncoding = forceEncoding;
}
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
if (this.encoding != null && (this.forceEncoding || request.getCharacterEncoding() == null)) {
//请求的设置
request.setCharacterEncoding(this.encoding);
if (this.forceEncoding) {
//响应的也设置
response.setCharacterEncoding(this.encoding);
}
}
filterChain.doFilter(request, response);
}
}
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">
<filter>
<filter-name>CharacterEncodingFilter</filter-name>
<filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
<init-param>
<param-name>encoding</param-name>
<param-value>UTF-8</param-value>
</init-param>
<init-param>
//响应的 也设置为 UTF-8
<param-name>forceEncoding</param-name>
<param-value>true</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>CharacterEncodingFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
<servlet>
<servlet-name>springDispatcherServlet</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>springDispatcherServlet</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
<filter>
<filter-name>HiddenHttpMethodFilter</filter-name>
<filter-class>org.springframework.web.filter.HiddenHttpMethodFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>HiddenHttpMethodFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
</web-app>
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/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.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.0.xsd">
<!-- 配置自动扫描到的包 -->
<context:component-scan base-package="com.atguigu.springmvc"></context:component-scan>
<!-- 配置视图解析器 -->
<bean id="viewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<property name="prefix" value="/WEB-INF/views/"></property>
<property name="suffix" value=".jsp"></property>
</bean>
<!-- 配置不经过处理器方法直接到达响应页面
path属性:设置要映射请求地址
view-name属性:设置视图名
-->
<mvc:view-controller path="/testViewResolver" view-name="success"/>
<!-- 配置了不经过处理器方法直接到达响应页面之后,处理器方法上的@RequestMapping将失效,此时必须配置以下标签 -->
<mvc:annotation-driven></mvc:annotation-driven>
</beans>
index
\WebContent\index.jsp
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Insert title here</title>
</head>
<body>
<form action="${pageContext.request.contextPath }/testPOJO" method="post">
<!-- 表单项中的name属性值要与POJO类中的属性名保持一致 -->
工号:<input type="text" name="id"><br>
姓名:<input type="text" name="lastName"><br>
邮箱:<input type="text" name="email"><br>
部门编号:<input type="text" name="dept.id"><br>
部门名称:<input type="text" name="dept.name"><br>
<input type="submit">
</form>
</body>
</html>
Controller
@Controller
public class SpringMVCHandler {
public static final String SUCCESS="success";
//1.SpringMVC中如何解决POST请求中文乱码问题,GET的又如何处理呢
/*
* ★测试入参为POJO
* Spring MVC会按请求参数名和 POJO属性名进行自动匹配,
* 自动为该对象填充属性值。支持级联属性
*/
@RequestMapping("/testPOJO")
public String testPOJO(Employee employee) {
System.out.println("员工的信息是:"+employee);
return SUCCESS;
}
}
04.简单的谈一下SpringMVC的工作流程
commons-logging-1.1.3.jar
jstl.jar
spring-aop-4.0.0.RELEASE.jar
spring-beans-4.0.0.RELEASE.jar
spring-context-4.0.0.RELEASE.jar
spring-core-4.0.0.RELEASE.jar
spring-expression-4.0.0.RELEASE.jar
spring-web-4.0.0.RELEASE.jar
spring-webmvc-4.0.0.RELEASE.jar
standard.jar
SpringMVCHandler
@Controller
public class SpringMVCHandler {
public static final String SUCCESS="success";
//1.简单的谈一下SpringMVC的工作流程
//处理模型数据方式一:将方法的返回值设置为ModelAndView
@RequestMapping("/testModelAndView")
public ModelAndView testModelAndView() {
//1.创建ModelAndView对象
ModelAndView mav = new ModelAndView();
//2.设置模型数据,最终会放到request域中
mav.addObject("user", "admin");
//3.设置视图
mav.setViewName("success");
return mav;
}
/*
* ★处理模型数据方式二:方法的返回值仍是String类型,在方法的入参中传入Map、Model或者ModelMap
* 不管将处理器方法的返回值设置为ModelAndView还是在方法的入参中传入Map、Model或者ModelMap,
* SpringMVC都会转换为一个ModelAndView对象
*/
@RequestMapping("/testMap")
public String testMap(Map<String , Object> map) {
//向Map中添加模型数据,最终会自动放到request域中
map.put("user", new Employee(1, "韩总", "hybing@atguigu.com", new Department(101, "教学部")));
return SUCCESS;
}
}
JSP
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Insert title here</title>
</head>
<body>
<a href="${pageContext.request.contextPath }/testModelAndView">Test ModelAndView</a><br>
<a href="${pageContext.request.contextPath }/testMap">Test Map</a><br>
</body>
</html>
springmvc.xml
<!-- 配置自动扫描到的包 -->
<context:component-scan base-package="com.atguigu.springmvc"></context:component-scan>
<!-- 配置视图解析器 -->
<bean id="viewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<property name="prefix" value="/WEB-INF/views/"></property>
<property name="suffix" value=".jsp"></property>
</bean>
<!-- 配置不经过处理器方法直接到达响应页面
path属性:设置要映射请求地址
view-name属性:设置视图名
-->
<mvc:view-controller path="/testViewResolver" view-name="success"/>
<!-- 配置了不经过处理器方法直接到达响应页面之后,处理器方法上的@RequestMapping将失效,此时必须配置以下标签 -->
<mvc:annotation-driven></mvc:annotation-driven>
success.jsp
WebContent/WEB-INF/views/success.jsp
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Insert title here</title>
</head>
<body>
<h1>Success Page</h1>
<h2>request域中的用户是:${requestScope.user }</h2>
</body>
</html>
流程
chain
英
/tʃeɪn/
n.
链,链条;一连串,一系列;连锁商店;镣铐;测链;连环式(指一群房主先售后购的置屋方式);<文>约束,束缚
v.
用锁链拴住;拘禁,束缚
-
- 请求过来后,到达
-
DispatcherServlet中央控制器
-
- 处理器映射器 找到 处理器
-
处理器映射器 HandlerMapping 的方法
-
- 得到:HandlerExecutionChain 处理执行链,里面有 所有的 处理器拦截器 Handlerintercepter 和 处理器对象
-
-
- 得到 处理器的适配器 HandlerAdapter
-
- 找对应的处理器,就是controller 处理请求
-
- 返回 ModelAndView
-
- 找对应的处理器,就是controller 处理请求
-
- 返回 ModelAndView,给中央处理器
-
- 通过配置的 viewResolver视图解析器,找到视图InternalResourceView
- InternalResourceViewResolver
-
- 返回视图InternalResourceView
-
-
- 渲染视图,将模型数据,给用户呈现出来。
- 响应用户
1、发送请求
- DispatcherServlet中央控制器
2、调用处理器映射器找到处理器
- HandlerMapping处理器映射器
3、返回HandlerExecutionChain 包含了所有的 拦截器和 处理器
- Handlerintercepter(处理器拦截器)
- Handlert(处理器对象)
4、通过处理器适配器调用具体的处理器
- HandlerAdapter(处理器适配器)
- 5、调用处理器
- Handler(即controller)
- 6、返回 ModelAndView
- HandlerAdapter(处理器适配器)
- 7、返回ModelAndView
- DispatcherServlet中央控制器
- 7、返回ModelAndView
- HandlerAdapter(处理器适配器)
- 5、调用处理器
8、视图解析
- viewResolver视图解析器 InternalResourceViewResolver
- 9、返回view
- DispatcherServlet中央控制器
- 9、返回view
10、渲染视图
- view视图
11、响应用户(依然是:DispatcherServlet中央控制器)
5.MyBatis属性名和表中的字段名不一样 ?
//MyBatis中当实体类中的属性名和表中的字段名不一样 ,怎么办
/*
* 解决方案:
* 1.写sql语句时起别名
* 2.在MyBatis的全局配置文件中开启驼峰命名规则
* 3.在Mapper映射文件中使用resultMap来自定义映射规则
*/
log4j-1.2.17.jar
mybatis-3.4.2.jar
mysql-connector-java-5.1.37-bin.jar
配置文件
- config/mybatis-config.xml
- map Under score To CamelCase
underscore
英
/ˌʌndəˈskɔː(r)/
v.
强调;在……的下面划线
n.
(尤指为强调)下划线
score
英
/skɔː(r)
n.
(游戏或比赛中的)得分,比分;(测验的)评分,分数;
v.
(在运动、比赛或考试中)得(分);评分,打分数;
Camel
英
/ˈkæm(ə)l/
n.
骆驼;驼绒织品;驼色,浅黄褐色;打捞浮筒
adj.
驼色的,暗棕色的
<?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>
<!-- <settings> -->
<!-- 开启驼峰命名规则 ,可以将数据库中的下划线映射为驼峰命名
例如:last_name可以映射为lastName
-->
<!-- <setting name="mapUnderscoreToCamelCase" value="true"/> -->
<!-- </settings> -->
<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="root" />
</dataSource>
</environment>
</environments>
<!-- 注册映射文件 -->
<mappers>
<mapper resource="EmployeeMapper.xml" />
</mappers>
</configuration>
- EmployeeMapper.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">
<!-- namespace属性:必须是接口的全类名 -->
<mapper namespace="com.atguigu.mybatis.mapper.EmployeeMapper">
<!--
id属性:必须是接口中方法的方法名
resultType属性:必须是方法的返回值的全类名
-->
<select id="getEmployeeById" resultMap="myMap">
select * from employees where id = #{id}
</select>
<!-- 自定义高级映射 -->
<resultMap type="com.atguigu.mybatis.entities.Employee" id="myMap">
<!-- 映射主键 -->
<id column="id" property="id"/>
<!-- 映射其他列 -->
<result column="last_name" property="lastName"/>
<result column="email" property="email"/>
<result column="salary" property="salary"/>
<result column="dept_id" property="deptId"/>
</resultMap>
</mapper>
- config/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>
实体类和接口
public class Employee {
private Integer id;
private String lastName;
private String email;
private double salary;
private Integer deptId;
//get set 全参构造,toString
}
public interface EmployeeMapper {
Employee getEmployeeById(Integer id);
}
测试类
@Test
void testGetEmployee() throws IOException {
//1.创建SqlSessionFactory对象
String resource = "mybatis-config.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
//2.获取sqlSession,sqlSession就相当于JDBC中的connection
SqlSession sqlSession = sqlSessionFactory.openSession();
try {
//3.获取Mapper对象
EmployeeMapper mapper = sqlSession.getMapper(EmployeeMapper.class);
//4.调用EmployeeMapper中获取Employee的方法
Employee employee = mapper.getEmployeeById(1);
System.out.println(employee);
} finally {
//5.关闭sqlSession
sqlSession.close();
}
}
SQL
/*
SQLyog Ultimate v11.25 (64 bit)
MySQL - 5.5.28 : Database - mybatis
*********************************************************************
*/
/*!40101 SET NAMES utf8 */;
/*!40101 SET SQL_MODE=''*/;
/*!40014 SET @OLD_UNIQUE_CHECKS=@@UNIQUE_CHECKS, UNIQUE_CHECKS=0 */;
/*!40014 SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0 */;
/*!40101 SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='NO_AUTO_VALUE_ON_ZERO' */;
/*!40111 SET @OLD_SQL_NOTES=@@SQL_NOTES, SQL_NOTES=0 */;
CREATE DATABASE /*!32312 IF NOT EXISTS*/`mybatis` /*!40100 DEFAULT CHARACTER SET gb2312 */;
USE `mybatis`;
/*Table structure for table `departments` */
DROP TABLE IF EXISTS `departments`;
CREATE TABLE `departments` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`name` varchar(100) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=6 DEFAULT CHARSET=gb2312;
/*Data for the table `departments` */
insert into `departments`(`id`,`name`) values (1,'教学部'),(2,'教务部'),(3,'运营部'),(4,'咨询部'),(5,'就业部');
/*Table structure for table `employees` */
DROP TABLE IF EXISTS `employees`;
CREATE TABLE `employees` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`last_name` varchar(100) DEFAULT NULL,
`email` varchar(100) DEFAULT NULL,
`salary` double(11,2) DEFAULT NULL,
`dept_id` int(11) DEFAULT NULL,
PRIMARY KEY (`id`),
KEY `dept_id` (`dept_id`),
CONSTRAINT `employees_ibfk_1` FOREIGN KEY (`dept_id`) REFERENCES `departments` (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=gb2312;
/*Data for the table `employees` */
insert into `employees`(`id`,`last_name`,`email`,`salary`,`dept_id`) values (1,'HanZong','hybing@atguigu.com',30000.00,1),(2,'Mayun','mayun@alibaba.com',20000.00,2),(3,'Mahuateng','mahuateng@qq.com',10000.00,3);
/*!40101 SET SQL_MODE=@OLD_SQL_MODE */;
/*!40014 SET FOREIGN_KEY_CHECKS=@OLD_FOREIGN_KEY_CHECKS */;
/*!40014 SET UNIQUE_CHECKS=@OLD_UNIQUE_CHECKS */;
/*!40111 SET SQL_NOTES=@OLD_SQL_NOTES */;