SSM框架汇总
- 什么是SSM框架
SSM是Spring、SpringMVC、Mybatis三种框架的首字母简写,
SSM是目前Java Web开发的最普遍和最高效框架,并且依然在不断发展与完善。
SSM是以Java语言为根基,再结合SQL语言发展而来,SSM本质上就是对曾经各种繁琐的Java和SQL开发进行封装与整合,使其开发难度大幅下降,提高效率。
所以SSM并不是一门类似编程语言那般的全新领域,而更像一种工具,想要用好这个工具,就要有扎实的Java以及SQL基础。
Spring框架对基础Java开发进行了封装整合,使其原本需要大量代码才能完成的任务变成只需少量的代码+必要的配置就能完成
SpringMVC框架对Spring和MVC三层架构进行了封装和整合,同样只需要少量的代码+必要的配置即可完成Java的开发,也进一步减少了Java Web开发的代码量以及难度
Mybatis框架则是对SQL进行了封装和整合,众所周知以往的SQL的开发都及其繁琐复杂,但Mybatis框架的出现使其变得简洁明了易上手,极大地减少了SQL语句量,也是只需要少量的SQL语句+必要的配置即可完成SQL开发
可以看出,SSM框架对传统Java以及SQL开发的封装整合思路是:将原本需要在各个代码块重复声明的配置信息单独拿出来放在一个配置文件中,代码块只保留核心功能代码,这样避免了“重复造轮子”,更使得整个项目工程变得层次分明逻辑清晰,极大的方便了项目的修改和维护工作的进行,由于SSM框架对Java核心通用功能的封装,似的开发核心功能代码时的代码量也大幅减少,SSM就是通过这种方式来将一个完整的Java项目变得简单明了易维护的。
开发者在使用SSM框架进行开发时必须遵守两个要点:
- 约定大于配置
- 遇事不决再加一层
为了保证项目工程中各个代码块之间的低耦合性,所以SSM框架中存在一些看似多余实则必要的规则,这些规则并不是
Java或SQL语言的规则,而是所有开发者共同约定的规则,目的就是为了降低代码耦合性,以便将来进行各种修改和维护。也是为了降低耦合性和修改以及维护的难度,SSM框架要求开发者尽可能的减少对原有代码的修改,尽量以增加新代码块的方式来进行修改和维护,就像电脑绘画一样,已经画好的部分尽量不动,而是通过不断增加图层的来完善项目。
- Spring框架
2.1 什么是Spring
Spring简介
Spring框架是由于软件开发的复杂性而创建的。Spring使用的是基本的JavaBean来完成以前只可能由EJB完成的事情。然而,Spring的用途不仅仅限于服务器端的开发。从简单性、可测试性和松耦合性角度而言,绝大部分Java应用都可以从Spring中受益。
目的:解决企业级应用开发的复杂性
功能:使用基本的JavaBean代替EJB,并提供更多的企业应用功能
范围:所有Java应用
Spring是一个轻量级的控制反转(IOC)和面向切面(AOP)的容器框架。
EJB框架:早年的一个机器笨重的框架
起源:2002年-interface21(框架雏形)
2004年3月21日-Spring 1.0
创始人:Rod Jahnson(罗德·约翰逊)
成就:Spring Framework
出版书籍:
《Expert One-on-One J2EE Design and Development》
【注】此人原本是个音乐学博士
Spring核心理念:使现有技术更加容易使用,同时整合更多的工具和框架来增加新特性
GitHub地址:
https://github.com/spring-projects/spring-framework/releases
Maven地址:
https://mvnrepository.com/artifact/org.springframework/spring-webmvc
优点:
- Spring是一个开源的免费框架
- Spring是一个轻量级、非入侵式的框架(不改变原代码)
- 控制反转(IOC)和面向切面(AOP)
- 对事务的支持和框架的整合支持非常优秀
【注】控制反转(IOC)和面向切面(AOP),必须掌握!!!!!!!
Spring七大模块
核心容器(Spring Core)
核心容器提供Spring框架的基本功能。Spring以bean的方式组织和管理Java应用中的各个组件及其关系。Spring使用BeanFactory来产生和管理Bean,它是工厂模式的实现。BeanFactory使用控制反转(IoC)模式将应用的配置和依赖性规范与实际的应用程序代码分开。
应用上下文(Spring Context)
Spring上下文是一个配置文件,向Spring框架提供上下文信息。Spring上下文包括企业服务,如JNDI、EJB、电子邮件、国际化、校验和调度功能。
Spring面向切面编程(Spring AOP)
通过配置管理特性,Spring AOP 模块直接将面向方面的编程功能集成到了 Spring框架中。所以,可以很容易地使 Spring框架管理的任何对象支持 AOP。Spring AOP 模块为基于 Spring 的应用程序中的对象提供了事务管理服务。通过使用 Spring AOP,不用依赖 EJB 组件,就可以将声明性事务管理集成到应用程序中。
JDBC和DAO模块(Spring DAO)
JDBC、DAO的抽象层提供了有意义的异常层次结构,可用该结构来管理异常处理,和不同数据库供应商所抛出的错误信息。异常层次结构简化了错误处理,并且极大的降低了需要编写的代码数量,比如打开和关闭链接。
对象实体映射(Spring ORM)
Spring框架插入了若干个ORM框架,从而提供了ORM对象的关系工具,其中包括了Hibernate、JDO和 IBatis SQL Map等,所有这些都遵从Spring的通用事物和DAO异常层次结构。
Web模块(Spring Web)
Web上下文模块建立在应用程序上下文模块之上,为基于web的应用程序提供了上下文。所以Spring框架支持与Struts集成,web模块还简化了处理多部分请求以及将请求参数绑定到域对象的工作。
MVC模块(Spring Web MVC)
MVC框架是一个全功能的构建Web应用程序的MVC实现。通过策略接口,MVC框架变成为高度可配置的。MVC容纳了大量视图技术,其中包括JSP、POI等,模型来有JavaBean来构成,存放于m当中,而视图是一个街口,负责实现模型,控制器表示逻辑代码,由c的事情。Spring框架的功能可以用在任何J2EE服务器当中,大多数功能也适用于不受管理的环境。Spring的核心要点就是支持不绑定到特定J2EE服务的可重用业务和数据的访问的对象,毫无疑问这样的对象可以在不同的J2EE环境,独立应用程序和测试环境之间重用。
2.2 控制反转(Inversion of Control,IOC)
什么是IOC思想
简而言之,就是将控制主动权从开发者移交到用户手中
例:以往的业务代码
Dao层
UserDao
public interface UserDao {
void getUser();
}
UserDaoImpl
public class UserDaoImpl implements UserDao{
@Override
public void getUser() {
System.out.println("Spring");
}
}
Service层
UserService
public interface UserService {
void getUser();
}
UserServiceImpl
public class UserServiceImpl implements UserService{
private UserDao userDao = new UserDaoImpl();
@Override
public void getUser() {
userDao.getUser();
}
}
这样的业务代码看似没毛病,前端只有调用业务层的方法就能输出"Spring",但众所周知,一千个人有一千个哈姆雷特,
如果有些用户不想看到"Spring",而想看到"MySQL"、"Oracle"、"SQLServer"呢?
增加Dao层实现类?
UserDaoMySQLImpl
public void getUser() {
System.out.println("MySQL");
}
UserDaoOracleImpl
public void getUser() {
System.out.println("Oracle");
}
UserDaoSQLServerImpl
public void getUser() {
System.out.println("SQLServer");
}
然后再根据用户不同的需求来修改Service层业务代码?
嗯,理论上也没毛病,但接下来才是最严重的问题
如果用户数量成千上万,并且还同时发起了请求,请问该怎么办?让同样数量的开发者一对一定制修改?
显然这是不可行的,这就是控制主动权在开发者手中的最大弊端!
显然,我们必须将控制主动权转交到用户手中才行,实现的方法很多,但核心其实很简单,就是在Service层使用set方法
UserServiceImpl
public class UserServiceImpl implements UserService{
private UserDao userDao;
public void setUserDao(UserDao userDao) {
this.userDao = userDao;
}
@Override
public void getUser() {
userDao.getUser();
}
}
UserTest
UserServiceImpl userService = new UserServiceImpl();
userService.setUserDao(new UserDaoXXXXImpl());
userService.getUser();
在前端页面给用户做选择,然后将结果传递回业务层,业务层根据传回的参数自动生成相应的userDao对并执行其方法,不同的用户的需求就能实时满足,毕竟谁都不喜欢网页半天显示不出来的感觉对吧
如此一来,Dao层和Service层就被解耦了,不再具有强耦合性,对Dao对象的创建更加灵活了,用户需求本更,开发者只需要修改Dao层即可,Service层基本不用动
现在控制所有权就由原来的开发者变为了用户,方向反过来了,这种思想就叫做IOC,控制反转,IOC也是Spring框架的核心!
2.3 HelloSpring
在Spring框架中创建对象
POJO实体类
Hello
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Hello {
private String name;
}
在resources文件下创建IOC容器配置文件
ApplicationContextBeans.xml(IOC容器配置文件)
以下是IOC容器配置文件的基本框架
【注】<beans>就是IOC容器
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd">
</beans>
在ApplicationContextBeans.xml中编写以下代码
<!--使用Spring的IOC容器来创建对象,在Spring中,对象统称为Bean
要使用IOC容器来创建对象,就要配置IOC容器的元数据
以往创建对象:类型 变量名 = new 类型();
如:Hello hello = new Hello();
在bean标签中
id:变量名
class:全路径表示的类型
property标签:给指定属性赋值
name:属性名
value:属性值
这就是Spring框架创建对象的方式
-->
<bean id="hello" class="com.OCTDN.pojo.Hello">
<property name="name" value="OCTDN"/>
</bean>
测试
MyTest
//获取Spring的上下文对象,本质是将IOC容器实例化
ClassPathXmlApplicationContext context = null;
context = new ClassPathXmlApplicationContext("ApplicationContextBeans.xml");
//原本需要创建的对象,现在都在Spring中管理了,使用的时候直接去Spring里获取即可
//创建Hello对象
//相当于:Hello hello = new Hello();
Hello hello = (Hello) context.getBean("hello");
System.out.println(hello.toString());
通过以上案例可以看出何为控制反转
- 控制
对于创建对象的控制劝,由原来的由程序本身创建
(new XXX()),变为了由Spring框架来创建
- 反转
程序本身不再创建对象,而是被动的接收对象
由此可以看出,使用Spring框架后,开发者再也不需要去进行繁琐的程序修改了,要实现不同的操作,只需要修改配置文件即可
一句话解释IOC:对象由Spring来创建、管理、装配
可用Spring框架来优化先前的例子
<bean id="spring" class="com.OCTDN.dao.UserDaoImpl"/>
<bean id="mysql" class="com.OCTDN.dao.UserDaoMySQLImpl"/>
<bean id="oracle" class="com.OCTDN.dao.UserDaoOracleImpl"/>
<bean id="sqlserver" class="com.OCTDN.dao.UserDaoSQLServerImpl"/>
<bean id="service" class="com.OCTDN.service.UserServiceImpl">
<property name="userDao" ref="spring"/>
</bean>
ref代表引用
UserTest
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("ApplicationContextBeans.xml");
UserServiceImpl service = (UserServiceImpl) context.getBean("service");
service.getUser();
通过Spring框架,实现了在实际使用和维护中,无论需求如何变更,都不需要再底层代码和用户代码了,只需要修改配置文件即可
2.4 IOC创建对象的方式
在先前的测试案例中,getBean()本质上就是创建对象的方法
众所周知,一个实体类中的构造方法分为有参和无参
使用无参构造方法创建对象
POJO
User
public class User {
private String name;
public User(){
System.out.println("User的无参构造方法");
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public void show(){
System.out.println("name=" + name);
}
}
ApplicationContextBeans.xml
<bean id="user" class="com.OCTDN.pojo.User">
</bean>
MyTest
ApplicationContext context = new ClassPathXmlApplicationContext("ApplicationContextBeans.xml");
User user = (User) context.getBean("user");
执行getBean()方法会创建一个User对象,所以无参构造方法会被执行
使用有参构造方法创建对象
一共三种方式
第一种:根据参数索引并为其赋值
有参构造方法
public User(String name){
this.name = name;
}
ApplicationContextBeans.xml
<bean id="user01" class="com.OCTDN.pojo.User">
<!--通过有参构造方法创建对象
index:参数下标,此处只有一个参数,所以下表为0
value:参数的值-->
<constructor-arg index="0" value="OCTDN01"/>
</bean>
MyTest
ApplicationContext context = new ClassPathXmlApplicationContext("ApplicationContextBeans.xml");
User user = (User) context.getBean("user01");
user.show();
第二种:根据参数类型并为其赋值
ApplicationContextBeans.xml
<bean id="user02" class="com.OCTDN.pojo.User">
<!--通过有参构造方法创建对象
type:参数的类型
value:参数的值-->
<constructor-arg type="java.lang.String" value="OCTDN02"/>
</bean>
MyTest
User user = (User) context.getBean("user02");
第三种:根据参数名称并为其赋值
ApplicationContextBeans.xml
<bean id="user03" class="com.OCTDN.pojo.User">
<!--通过有参构造方法创建对象
name:参数的名称
value:参数的值-->
<constructor-arg name="name" value="OCTDN03"/>
</bean>
MyTest
User user = (User) context.getBean("user03");
【注】如果参数是对象,就要使用以下方法来创建对象
<beans>
<bean id="beanOne" class="x.y.ClassOne">
<constructor-arg ref="beanTwo"/>
<constructor-arg ref="beanThree"/>
</bean>
<bean id="beanTwo" class="x.y.ClassTwo"/>
<bean id="beanThree" class="x.y.ClassThree"/>
</beans>
【WARNING】
这里隐藏着一个很严重的问题,如果存在多个实体类,且每个实体类都有无参构造函数会怎么样
Test
public Test(){
System.out.println("Test被创建了");
}
Text
public Text(){
System.out.println("Text被创建了");
}
ApplicationContextBeans.xml
<bean id="test" class="com.OCTDN.pojo.Test"></bean>
<bean id="text" class="com.OCTDN.pojo.Text"></bean>
MyTest
User user01 = (User) context.getBean("user01");
user01.show();
测试结果:
这就非常尴尬了,我们只想创建User对象,结果Test和Text也被创建了,这是因为IOC容器实际上相当于一个对象池,在使用ClassPathXmlApplicationContext("XXXX.xml")加载配置文件时,IOC容器中的所有对象都会被初始化,所以Test和Text的无参构造方法就会被调用
如果再创建一个配置文件呢
Beans.xml
<bean id="test" class="com.OCTDN.pojo.Test"></bean>
MyTest
ApplicationContext context = new ClassPathXmlApplicationContext("Beans.xml");
Test test = (Test) context.getBean("test");
测试结果:
总结:如有必要,则必须将对象的元数据单独存放在一个IOC容器(XXXX.xml配置文件)中
2.5 Spring配置(表皮)
别名(没啥用)
例:
<bean id="test" class="com.OCTDN.pojo.Test"></bean>
<!--别名-->
<alias name="test" alias="aaaaa"/>
Bean配置
bean:Spring的对象标签
id:bean的唯一标识符,相当于对象名
class:bean对象的全限定名:包名+类名
name:更好用的别名,可取多个别名,别名间可用逗号、空格或分号分隔
property:对象内的属性
name:属性名
value:属性值
import
一般用于团队开发,可将多个配置文件导入合并
将多个配置文件导入到一个总配置文件中,只需要加载总配置文件,即可使用所有的配置
ApplicationContextBeans.xml
<import resource="Beans01.xml"></import>
<import resource="Beans02.xml"></import>
<import resource="Beans03.xml"></import>
2.6 DI依赖注入(Dependency Injection)
什么是依赖注入
依赖:bean对象的创建依赖于Spring容器
注入:bean对象中的所有属性,都由Spring容器来注入
构造器注入
就是前面的创建对象的方式
set注入(重点)
1、环境搭建
(1)复杂类型
private String address;
public String getAddress() {
return address;
}
public void setAddress(String address) {
this.address = address;
}
- 真实测试对象
private Address address;
private String name;
private String[] book;
private List<String> hobbys;
private HashMap<String,String> code;
private Set<String> games;
private String girlfriend;
private Properties info;
各种注入方式
ApplicationContextBeans.xml
<bean id="address" class="com.OCTDN.pojo.Address"></bean>
<bean id="student" class="com.OCTDN.pojo.Student">
<!--普通注入:value-->
<property name="name" value="OCTDN"></property>
<!--Bean注入,ref:引用-->
<property name="address" ref="address"></property>
<!--普通数组注入,array-->
<property name="book">
<array>
<value>书籍A</value>
<value>书籍B</value>
<value>书籍C</value>
<value>书籍D</value>
</array>
</property>
<!--List数组注入,List-->
<property name="hobbys">
<list value-type="java.lang.String">
<value>爱好A</value>
<value>爱好B</value>
<value>爱好C</value>
<value>爱好D</value>
</list>
</property>
<!--Map集合注入,entiy:实体(键值对)-->
<property name="code">
<map key-type="java.lang.String" value-type="java.lang.String">
<entry key="身份证" value="1111111111111111111111111"/>
<entry key="银行卡" value="2222222222222222222222222"/>
</map>
</property>
<!--Set注入-->
<property name="games">
<set value-type="java.lang.String">
<value>死或生</value>
<value>奴隶少女</value>
<value>缘之空</value>
<value>炒粉</value>
<value>惩戒魅魔</value>
</set>
</property>
<!--空指针注入-->
<property name="girlfriend">
<null></null>
</property>
<!--Properties注入,比如JDBC-->
<property name="info">
<props>
<prop key="学号">114514</prop>
<prop key="性别">男</prop>
<prop key="姓名">野兽先辈</prop>
</props>
</property>
</bean>
其他方式
C命名空间
本质是使用第三方约束来简化构造器注入
例:
ApplicationContextBeans.xml
..........................................
xmlns:c="http://www.springframework.org/schema/c"
..........................................
<!--C命名空间注入-->
<bean id="person" class="com.OCTDN.pojo.Person" c:name="OCTDN" c:age="25"></bean>
P命名空间
本质是使用第三方约束来简化Set注入
例:
ApplicationContextBeans.xml
..........................................
xmlns:p="http://www.springframework.org/schema/p"
..........................................
<!--P命名空间注入-->
<bean id="user" class="com.OCTDN.pojo.User" p:name="OCTDN" p:age="25" ></bean>
Bean的作用域(重点!!!!!!!!!!)
Scope | Description |
(默认)将每个 Spring IoC 容器的单个 bean 定义范围限定为单个对象实例。(单例模式) | |
将单个 bean 定义的作用域限定为任意数量的对象实例。 | |
将单个 bean 定义的范围限定为单个 HTTP 请求的生命周期。也就是说,每个 HTTP 请求都有一个在单个 bean 定义后面创建的 bean 实例。仅在可感知网络的 Spring ApplicationContext中有效。 | |
将单个 bean 定义的范围限定为 HTTP Session的生命周期。仅在可感知网络的 Spring ApplicationContext上下文中有效。 | |
将单个 bean 定义的范围限定为ServletContext的生命周期。仅在可感知网络的 Spring ApplicationContext上下文中有效。 | |
将单个 bean 定义的范围限定为WebSocket的生命周期。仅在可感知网络的 Spring ApplicationContext上下文中有效。 |
- 单例模式(默认,推荐单线程使用)
<bean id="text" class="com.OCTDN.pojo.Text" scope="singleton"></bean>
- 原型模式(推荐多线程使用)
每一次创建都是一个新的对象实例
<bean id="test02" class="com.OCTDN.pojo.Test" scope="prototype"></bean>
其余的只有在Web开发时才会用上
2.7 Bean的自动装配
Spring会自动在上下文中寻找并给bean装配属性
一共三种自动装配方式
- 在xml中显式配置bean(就是前面的内容)
- 在java中显式配置bean
- 隐式自动配置bean(重点)
环境搭建
三个实体类
People
private Dog dog;
private Cat cat;
private String name;
Gatter、Setter、toString
Cat
public class Cat {
public void bark(){
System.out.println("喵!");
}
}
Dog
public class Dog {
public void bark(){
System.out.println("汪!");
}
}
ByName自动装配(bean的id必须全局唯一)
autowire="byName",会根据目标类中的Setter方法来自动寻找xml中与Setter方法的值相同的bean的id并装配,不需要在property中手动配置引用
ApplicationContextBeans.xml
<bean id="cat" class="com.OCTDN.pojo.Cat"></bean>
<bean id="dog" class="com.OCTDN.pojo.Dog"></bean>
<bean id="people" class="com.OCTDN.pojo.People" autowire="byName">
<property name="name" value="OCTDN"></property>
</bean>
MyTest
ApplicationContext context = new ClassPathXmlApplicationContext("ApplicationContextBeans.xml");
People people = context.getBean("people", People.class);
people.getDog().bark();
people.getCat().bark();
ByType自动装配(bean的class必须全局唯一)
<bean id="people" class="com.OCTDN.pojo.People" autowire="byType">
使用注解实现自动装配(重点!!!!!!!!!!)
要使用注解实现自动装配,就必须先配置相关约束
<beans
...................................................
xmlns:context="http://www.springframework.org/schema/context"
...................................................
http://www.springframework.org/schema/context
https://www.springframework.org/schema/context/spring-context.xsd">
<context:annotation-config/>
@Autowired实现自动装配
@Autowired会根据对象名在配置上下文中自动寻找并装配bean
在对象属性上使用(如此可以省略掉Dog和Cat的set方法)
People
@Autowired
private Dog dog;
@Autowired
private Cat cat;
也可以在Setter方法上使用
People
@Autowired
public void setDog(Dog dog) {
this.dog = dog;
}
@Autowired
public void setCat(Cat cat) {
this.cat = cat;
}
【注】
使用前必须将实体类在IOC容器中注册到bean中,且id与对象名相同,@Autowired是通过ByType的方式实现的,所以目标对象必须在IOC容器中注册bean
有时也会存在一些特例
如:
<bean id="dog2" class="com.OCTDN.pojo.Dog"></bean>
<bean id="dog2222" class="com.OCTDN.pojo.Dog"></bean>
这时@Autowired就会找不到dog,所以需要手动显式指定目标bean的id
@Autowired
@Qualifier(value = "dog2")
private Dog dog;
2.8 Spring注解开发
在Spring4之后,要使用注解开发,就必须导入AOP包
需要导入注解的支持
【注】注解只适合简单配置,复杂配置还是推荐使用xml
注解约束:
...................................................
xmlns:context="http://www.springframework.org/schema/context"
...................................................
http://www.springframework.org/schema/context
https://www.springframework.org/schema/context/spring-context.xsd">
<!--指定需要扫描的软件包,指定包下的注解会自动生效-->
<context:component-scan base-package="com.OCTDN"/>
<!--加载注解驱动-->
<context:annotation-config/>
Bean
User
//将该类标记为组件,相当于<bean id = "user" class = "com.OCTDN.pojo.User" />
@Component
public class User {
public String name = "OCTDN";
}
属性注入
User
//相当于<property name = "name" value = "OCTDN">
@Value("OCTDN")
public String name;
或
//相当于<property name = "name" value = "OCTDN">
@Value("OCTDN")
public void setName(String name) {
this.name = name;
}
@Component衍生的注解
@Component对应MVC三层架构衍生出了三种注解,但功能是一样的,都是将指定类注册到Spring容器中并装配Bean
Dao【@Repository】
Service【@Service】
Servlet【@Controller】
自动装配
往前翻
作用域
和Bean的作用于基本相似
User
单例模式
@Scope("singleton")
public class User {
原型模式
@Scope("prototype")
public class User {
2.9使用Java配置Spring
在Spring4之后,全部都推荐使用JavaConfig来配置Spring
不再使用xml来配置,而是全权交由Java
例:
User
@Component
public class User {
private String name;
public String getName() {
return name;
}
@Value("OCTDN")
public void setName(String name) {
this.name = name;
}
toString
Cat
@Component
public class Cat {
public void brak(){
System.out.println("喵!");
}
}
Dog
@Component
public class Dog {
public void brak(){
System.out.println("汪!");
}
}
配置类(相当于xml)
/*本身也是个配置,也会被注册到Spring容器中,代表这是一个配置类*/
@Configuration
//显式扫描指定包
@ComponentScan("com.OCTDN")
//导入其他类,添加Bean
@Import(Cat.class)
public class Config {
/*相当于注册了一个Bean,<bean id = "" class = ""/>
id:方法名
class:返回值
*/
@Bean
public User getUser(){
return new User();
}
//一个配置类中可以有多个Bean
@Bean
public Dog getDog(){
return new Dog(); }}
测试:
MyTest
/*
* 通过AnnotationConfigApplicationContext来获取上下文中的容器,通过配置类.class来加载配置类
* */
ApplicationContext context = new AnnotationConfigApplicationContext(Config.class);
User getUser = context.getBean("getUser", User.class);
System.out.println(getUser.getName());
Dog getDog = context.getBean("getDog", Dog.class);
getDog.brak();
//导入的类的bean的id默认为类名小写
Cat getCat = context.getBean("cat", Cat.class);
getCat.brak();
2.10 代理模式
什么是代理模式
代理模式是Spring AOP的底层
代理模式的分类:
1、静态代理
2、动态代理
静态代理
角色分析
- 抽象角色
抽象类或接口
- 代理角色
代理真实角色,代理后会做一些附属操作
- 真实角色
被代理的角色
- 客户
访问代理角色的人
静态代理的好处:
分工明确,高扩展性,易维护
静态代理的坏处:
代码量翻倍,开发效率低下
例:
Rent
//出租房子这件事情
public interface Rent {
public void rent();
}
Landlord
//房东,想要出租房子
public class Landlord implements Rent{
@Override
public void rent() {
System.out.println("房东要出租房子");
}
}
Proxy
//中介,帮房东出租房子,同时会有额外操作
public class Proxy implements Rent{
private Landlord landlord = new Landlord();
@Override
public void rent() {
landlord.rent();
}
public void look(){
System.out.println("带客户看房子");
}
public void money(){
System.out.println("收取中介费");
}
}
Client
//客户要租房子
public class Client {
public static void main(String[] args) {
//找中介
Proxy proxy = new Proxy();
//中介表示有房可以租
proxy.rent();
//带客户看房
proxy.look();
//并收取中介费
proxy.money();
}
}
动态代理(比较难!!!!!!!!!)
本质上是通过反射机制来实现
使用动态就不需要再一一手写代理类了
例:
ProxyInvocationHandler
//生成代理类的工具类
public class ProxyInvocationHandler implements InvocationHandler {
//被代理的接口
private Object target;
public void setTarget(Object target) {
this.target = target;
}
//自动生成代理类
public Object getProxy(){
return Proxy.newProxyInstance(
//用哪个类加载器来加载代理对象
this.getClass().getClassLoader(),
//动态代理需要实现的接口
target.getClass().getInterfaces(),
//动态代理在执行时,会调用指定目标的invoke方法并执行
this);}
//调用被代理的接口的方法
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
Object result = method.invoke(target, args);
return result;
}
}
Client
//真实角色实现了被代理的接口
Landlord landlord = new Landlord();
//获取动态生成代理类的工具类对象
ProxyInvocationHandler handler = new ProxyInvocationHandler();
//设置被代理对象
handler.setTarget(landlord);
//动态生成代理类对象,类型为被代理的接口
Rent proxy = (Rent) handler.getProxy();
//执行被代理对象的方法
proxy.rent();
动态代理的优点:
- 分工明确,高扩展性,易维护
- 一个动态代理类代理的是一个接口,对应了一类业务
- 一个动态代理类可以所有实现了目标接口的实现类
2.11 AOP(超级重点!!!!!!!!!)
AOP的本质就是使用反射机制实现的动态代理
什么是AOP
AOP(Aspect Oriented Programming),意为:面向切面编程,通过预编译方式和运行期间动态代理实现程序功能的统一维护的一种技术。AOP是OOP的延续,是软件开发中的一个热点,也是Spring框架中的一个重要内容,是函数式编程的一种衍生范型。利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。
AOP在Spring中的作用
AOP提供了声明式事务,允许用户自定义切面
横切关注点:
跨越应用程序多个模块的方法或功能,即与我们业务逻辑无关,但需要被关注的部分,如日志、安全、缓存.......
切面(ASPECT):
横切关注点被模块化的特殊对象,切面是一个类
通知(Advice):
切面必须要完成的工作,即切面类中的方法
目标(Target):
被通知的对象
代理(Proxy):
向目标对象收到通知之后创建的对象
切入点(PointCut):
对切面通知执行的“地点”的定义
连接点(JoinPoint):
与切入点匹配的执行点
在Spring AOP中,是通过Advice定义横切逻辑的
Spring中支持以下五种类型的Advice
1、前置通知:方法前
2、后置通知:方法后
3、环绕通知:方法前后
4、异常抛出通知:方法抛出异常
5、引介通知:类中增加新的方法属性
使用Spring实现AOP
1、导入Maven依赖
<!--AOP-->
<!-- https://mvnrepository.com/artifact/org.aspectj/aspectjweaver -->
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.9.8.M1</version>
</dependency>
- 使用Spring实现AOP有种方式
方式一:使用Spring的AOP的API接口
导入相关约束
....................................
xmlns:aop="http://www.springframework.org/schema/aop"
....................................
http://www.springframework.org/schema/aop
https://www.springframework.org/schema/aop/spring-aop.xsd
例:
UserService
public void add();
public void delete();
public void update();
public void query();
UserServiceImpl
@Override
public void add() {
System.out.println("增加用户");
}
BeforeLog
//前置,会在目标方法执行前被调用执行
public class BeforeLog implements MethodBeforeAdvice {
/*
* method:要执行的目标对象的方法
* objects:参数(args)
* o:目标对象(target)
* */
@Override
public void before(Method method, Object[] objects, Object o) throws Throwable {
System.out.println(
o.getClass().getName()+"的"+method.getName()+"方法被执行了"
);}}
AfterLog
//返回后置,会在目标方法执行并返回后调用并执行
public class AfterLog implements AfterReturningAdvice {
/*
* method:要执行的目标对象的方法
* objects:参数(args)
* o:返回值
* o1:目标对象(target)
* */
@Override
public void afterReturning(Object o, Method method, Object[] objects, Object o1) throws Throwable {
System.out.println(
o1.getClass().getName()+"的"+method.getName()+"执行完毕并返回了"
+o
);
}
}
beans.xml
<bean id="after" class="com.OCTDN.log.AfterLog"></bean>
<bean id="before" class="com.OCTDN.log.BeforeLog"></bean>
<bean id="user" class="com.OCTDN.Service.UserServiceImpl"></bean>
<!--配置AOP-->
<aop:config>
<!--pointcut:切入点,在什么地方执行AOP方法
id:切入点名
expression:表达式
execution(需要执行的位置:修饰词 返回值 类名 方法名 参数)
如:* com.OCTDN.service.UserServiceImpl.*(..)
*:任意修饰词和返回值
com.OCTDN.service.UserServiceImpl:类名
.*:任意方法
(..):任意参数
-->
<aop:pointcut id="pointcut" expression="execution(* com.OCTDN.Service.UserServiceImpl.*(..))"/>
<!--环绕通知(advisor)
advice-ref:用XXXX做切入
pointcut-ref:往XXXXX做切入
用前置通知(before)做切入,切入到id为pointcut的切入点中
-->
<aop:advisor advice-ref="before" pointcut-ref="pointcut"/>
<aop:advisor advice-ref="after" pointcut-ref="pointcut"/>
</aop:config>
MyTest
ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
UserService user = context.getBean("user", UserService.class);
user.add();
方式二:使用自定义类实现AOP(切面类定义)
DiyPointCut
//自定义切入面类
public class DiyPointCut {
public void before(){
System.out.println("方法执行前");
}
public void after(){
System.out.println("方法执行后");
}
}
beans.xml
<!--方式二:自定义类-->
<bean id="diypointcut" class="com.OCTDN.diy.DiyPointCut"/>
<aop:config>
<!--配置自定义切面类-->
<aop:aspect ref="diypointcut">
<aop:pointcut id="pointcut" expression="execution(* com.OCTDN.Service.UserServiceImpl.*(..))"/>
<aop:before method="before" pointcut-ref="pointcut"/>
<aop:after method="after" pointcut-ref="pointcut"/>
</aop:aspect>
</aop:config>
很明显,第一种方式虽然麻烦且难懂,但功能更加齐全
方式三:使用注解实现AOP
AnnotationPointCut
//使用注解实现AOP
//标注这是一个切面类
@Aspect
@Component
public class AnnotationPointCut {
@Before("execution(* com.OCTDN.Service.UserServiceImpl.*(..))")
public void before(){
System.out.println("方法执行前01");
}
@After("execution(* com.OCTDN.Service.UserServiceImpl.*(..))")
public void after(){
System.out.println("方法执行后02");
}
}
beans.xml
<!--开启注解支持-->
<aop:aspectj-autoproxy/>
AOP是一种横向编程的思想,就是在不修改原业务类的情况下,实现项目的动态增强,正常项目都是由底层向上纵向开发,如果要修改的话会牵扯非常多的代码,但AOP就像一个新模块,直接横切进需要的地方即可,几乎不用修改原代码,这样对后期维护非常好,AOP的本质就是通过反射机制实现的动态代理。
2.12 在Spring中整合Mybatis
回顾Mybatis
1、导入Maven依赖
<!--Mybatis-Spring-->
<!-- https://mvnrepository.com/artifact/org.mybatis/mybatis-spring -->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis-spring</artifactId>
<version>2.0.6</version>
</dependency>
2、构建基本的Mybatis
(1)创建数据库并连接IDEA
自己弄!
(2)编写对应实体类
@Data
@NoArgsConstructor
@AllArgsConstructor
public class User {
private int id;
private String name;
private String password;
}
- 编写核心配置文件
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>
<!--引入外部配置文件-->
<properties resource="db.properties"/>
<settings>
<setting name="logImpl" value="STDOUT_LOGGING"/>
<!--显式开启二级缓存-->
<setting name="cacheEnabled" value="true"/>
</settings>
<typeAliases>
<typeAlias alias="User" type="com.OCTDN.pojo.User"/>
</typeAliases>
<!--环境-->
<environments default="development">
<environment id="development">
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<property name="driver" value="${driver}"/>
<property name="url" value="${url}"/>
<property name="username" value="${username}"/>
<property name="password" value="${password}"/>
</dataSource>
</environment>
</environments>
<mappers>
<mapper resource="com/OCTDN/dao/UserMapper.xml"></mapper>
</mappers>
</configuration>
db.properties
driver=com.mysql.jdbc.Driver
url=jdbc:mysql://localhost:3306/mybatis?useSSL=true&useUnicode=true&characterEncoding=UTF-8
username=root
password=964878
- 编写接口类
UserMapper
public interface UserMapper {
public List<User> query();
public int add();
public int delete();
public int update();
}
(5)编写Mapper.xml
<mapper namespace="com.OCTDN.dao.UserMapper">
<select id="query" resultType="User">
select * from mybatis.user
</select>
</mapper>
MyTest
//回顾Mybatis
//配置文件名
String resources = "mybatis-config.xml";
//加载配置文件
InputStream resourceAsStream = Resources.getResourceAsStream(resources);
//创建SqlSession
SqlSessionFactory build = new SqlSessionFactoryBuilder().build(resourceAsStream);
//开启SqlSession并开启事务自动提交
SqlSession sqlSession = build.openSession(true);
//获取Mapper
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
//执行方法
List<User> query = mapper.query();
//遍历List数组并输出
for (User user : query) {
System.out.println(user);
}
//关闭SqlSession
sqlSession.close();
Mybatis-Spring
1、什么是Mybatis-Spring
MyBatis-Spring 会帮助你将 MyBatis 代码无缝地整合到 Spring 中。它将允许 MyBatis 参与到 Spring 的事务管理之中,创建映射器 mapper 和 SqlSession 并注入到 bean 中,以及将 Mybatis 的异常转换为 Spring 的 DataAccessException。 最终,可以做到应用代码不依赖于 MyBatis,Spring 或 MyBatis-Spring。
2、使用Mybatis-Spring
要和 Spring 一起使用 MyBatis,需要在 Spring 应用上下文中定义至少两样东西:SqlSessionFactory和DataSource(数据源)
- 编写数据源
<!--DataSource,使用Spring的数据源替代Mybatis的配置
class使用Spring提供的JDBC数据源包:
org.springframework.jdbc.datasource.DriverManagerDataSource
-->
<bean id="datasource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<!--配置JDBC连接-->
<property name="driverClassName" value="com.mysql.jdbc.Driver"/>
<!--这里的&需要换成&;-->
<property name="url" value="jdbc:mysql://localhost:3306/mybatis?useSSL=true&useUnicode=true&characterEncoding=UTF-8"/>
<property name="username" value="root"/>
<property name="password" value="964878"/>
</bean>
- SqlSessionFactory
<!--配置SqlSessionFactory来创建SqlSession-->
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<property name="dataSource" ref="datasource"/>
<!--绑定Mybatis配置-->
<property name="configLocation" value="classpath:mybatis-config.xml"/>
<property name="mapperLocations" value="classpath:com/OCTDN/dao/*.xml"/>
</bean>
- SqlSessionTemplate
<!--获取SqlSession,SqlSessionTemplate:SqlSession模板-->
<bean id="SqlSession" class="org.mybatis.spring.SqlSessionTemplate">
<!--SqlSessionTemplate需要注入SqlSession,只能通过构造方法注入-->
<constructor-arg index="0" ref="sqlSessionFactory"/>
</bean>
<!--注册UserMapperImpl-->
<bean id="userMapper" class="com.OCTDN.dao.UserMapperImpl">
<property name="sqlSessionTemplate" ref="SqlSession"/>
</bean>
(4)接口实现类
UserMapperImpl
//以往Mybatis操作都使用SqlSession来执行,现在改为sqlSessionTemplate
private SqlSessionTemplate sqlSessionTemplate;
public void setSqlSessionTemplate(SqlSessionTemplate sqlSessionTemplate) {
this.sqlSessionTemplate = sqlSessionTemplate;
}
@Override
public List<User> query() {
UserMapper mapper = sqlSessionTemplate.getMapper(UserMapper.class);
List<User> query = mapper.query();
return query;
}
测试
MyTest
ApplicationContext context = new ClassPathXmlApplicationContext("spring-dao.xml");
UserMapper userMapper = context.getBean("userMapper", UserMapper.class);
List<User> query = userMapper.query();
for (User user : query) {
System.out.println(user);
}
2.13 事务
事务的核心:要么都成功,要么都失败
ACID原则:
- 原子性
一个事务要么全部提交成功,要么全部失败回滚,不能只执行其中的一部分操作
- 一致性
事务的执行不能破坏数据库数据的完整性和一致性,一个事务在执行之前和执行之后,数据库都必须处于一致性状态
- 隔离性
事务的隔离性是指在并发环境中,并发的事务时相互隔离的,一个事务的执行不能不被其他事务干扰
- 持久性
一旦事务提交,那么它对数据库中的对应数据的状态的变更就会永久保存到数据库中
声明式事务(结合AOP)
导入事务支持
xmlns:tx="http://www.springframework.org/schema/tx"
和
http://www.springframework.org/schema/tx
https://www.springframework.org/schema/tx/spring-tx.xsd
- 配置声明式事务
<!--配置声明式事务-->
<bean id="tracsaction" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<constructor-arg ref="datasource"/>
</bean>
- 通过AOP实现事务管理
<!--结合AOP实现事务管理-->
<!--配置事务通知-->
<tx:advice id="transactionInterceptor" transaction-manager="tracsaction">
<!--声明给那些方法配置事务管理-->
<tx:attributes>
<!--给所有方法配置事务管理
propagation:事务的传播特性,默认值为REQUIRED
read-only="true":设置为只读
-->
<tx:method name="*" propagation="REQUIRED"/>
</tx:attributes>
</tx:advice>
- 配置事务的切入
<!--配置事务切入
pointcut:配置切入点
advisor:切入
-->
<aop:config>
<aop:pointcut id="txpointcut" expression="execution(* com.OCTDN.dao.*.*(..))"/>
<aop:advisor advice-ref="transactionInterceptor" pointcut-ref="txpointcut"/>
</aop:config>
- SpringMVC框架
3.1 回顾MVC
M:模型(Model),如:Dao,Service
V:视图(View),如:JSP
C:控制器(Controller),如:Servlet
1、MVC是一种软件设计规范,是将业务逻辑、数据、显示以三者分立的方式来组织代码
2、MVC的主要作用是降低了视图与业务逻辑间的双向耦合
3、MVC不是一种设计模式,而是一种架构模式,不同的MVC之间存在客观差异
MVC框架要做的事情
1、将url映射到java类或java类的方法中
2、封装用户提交的数据
3、处理请求--调用相关的业务处理--封装响应的数据
4、对响应的数据进行渲染
3.2 回顾Servlet
什么是Servlet
Java Servlet 是运行在 Web 服务器或应用服务器上的程序,它是作为来自 Web 浏览器或其他 HTTP 客户端的请求和 HTTP 服务器上的数据库或应用程序之间的中间层。
使用 Servlet,您可以收集来自网页表单的用户输入,呈现来自数据库或者其他源的记录,还可以动态创建网页。
Java Servlet 通常情况下与使用 CGI(Common Gateway Interface,公共网关接口)实现的程序可以达到异曲同工的效果。
编写Servlet
doGet
//获取前端参数
String method = req.getParameter("method");
if (method.equals("add")){
req.getSession().setAttribute("msg","执行了add方法");
} else {
if (method.equals("delete")){
req.getSession().setAttribute("msg","执行了delete方法");
}
}
//调用业务层
//重定向或转发
//请求转发
req.getRequestDispatcher("/WEB-INF/jsp/Test.jsp").forward(req,resp);
}
Test.jsp
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>Title</title>
</head>
<body>
<h1>${msg}</h1>
</body>
</html>
index.jsp
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>$Title$</title>
</head>
<body>
$END$
<form action="/test" method="get">
<div>
<p><input name="method" type="text"></p>
<p><input type="submit"></p>
</div>
</form>
</body>
</html>
注册Servlet
web.xml
<servlet>
<servlet-name>test</servlet-name> <servlet-class>com.OCTDN.servlet.HelloServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>test</servlet-name>
<url-pattern>/test</url-pattern>
</servlet-mapping>
3.3 SpringMVC
什么是SpringMVC
Spring Web MVC是一种基于Java的实现了Web MVC设计模式的请求驱动类型的轻量级Web框架,即使用了MVC架构模式的思想,将web层进行职责解耦,基于请求驱动指的就是使用请求-响应模型,框架的目的就是帮助我们简化开发,Spring Web MVC也是要简化我们日常Web开发的。
SpringMVC的优点
1、轻量级,简单易学
2、高效,基于请求响应的MVC框架
3、与Spring兼容性好,无缝结合
4、约定大于配置
5、功能强大:RESTful、数据验证、格式化、本地化、主题等等
6、简洁灵活
7、用的人多
SpringMVC是围绕DispatcherServlet(Servlet调度器)设计的,而DispatcherServlet本质也是一个Servlet,是一个智能版的Servlet
SpringMVC原理
例:
web.xml
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
version="4.0">
<!--1.注册DispatcherServlet-->
<servlet>
<servlet-name>springmvc</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<!--关联一个springmvc的配置文件:【servlet-name】-servlet.xml-->
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:springmvc-servlet.xml</param-value>
</init-param>
<!--启动级别-1-->
<load-on-startup>1</load-on-startup>
</servlet>
<!--/ 匹配所有的请求;(不包括.jsp)-->
<!--/* 匹配所有的请求;(包括.jsp)-->
<servlet-mapping>
<servlet-name>springmvc</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
</web-app>
springmvc-servlet.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">
<!--处理器映射器-->
<bean class="org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping"/>
<!--处理器适配器-->
<bean class="org.springframework.web.servlet.mvc.SimpleControllerHandlerAdapter"/>
<!--视图解析器:DispatcherServlet给他的ModelAndView-->
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver" id="InternalResourceViewResolver">
<!--前缀-->
<property name="prefix" value="/WEB-INF/jsp/"/>
<!--后缀-->
<property name="suffix" value=".jsp"/>
</bean>
<!--Handler-->
<bean id="/hello" class="com.OCTDN.servlet.HelloController"/>
</beans>
HelloController
public class HelloController implements Controller {
public ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response) throws Exception {
//ModelAndView 模型和视图
ModelAndView mv = new ModelAndView();
//封装对象,放在ModelAndView中。Model
mv.addObject("msg","HelloSpringMVC!");
//封装要跳转的视图,放在ModelAndView中
mv.setViewName("hello"); //: /WEB-INF/jsp/hello.jsp
return mv;
}}
hello.jsp
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>Title</title>
</head>
<body>
${msg}
</body>
</html>
SpringMVC执行流程大致解析
- 用户发送请求给请求调度器(DispatcherServlet)
<!--/ 匹配所有的请求;(不包括.jsp)-->
<!--/* 匹配所有的请求;(包括.jsp)-->
<servlet-mapping>
<servlet-name>springmvc</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
在web.xml中的这段配置,会让SpringMVC处理所有的请求,但不包括jsp
- 请求调度器将请求传递给处理器(Handler)
<!--Handler-->
<bean id="/hello" class="com.OCTDN.servlet.HelloController"/>
</beans>
=====================================================================
public class HelloController implements Controller {
public ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response) throws Exception {
//ModelAndView 模型和视图
ModelAndView mv = new ModelAndView();
//封装对象,放在ModelAndView中。Model
mv.addObject("msg","HelloSpringMVC!");
//封装要跳转的视图,放在ModelAndView中
mv.setViewName("hello"); //: /WEB-INF/jsp/hello.jsp
return mv;
}}
在web.xml中Handler的配置,会让请求调度器将请求传递给处理器类,处理器类实现了Controller接口的
ModelAndView handleRequest(HttpServletRequest var1, HttpServletResponse var2) throws Exception;
方法,本质就是处理Request和Response,和Servlet是一样的。在处理器类中创建一个ModelAndView对象,正如其名,模型和视图,在MVC中,模型层代表数据处理,视图层代表前端页面,所以ModelAndView的作用就是携带经过处理的数据以及需要显示数据的页面名称,相当于送快递,处理器类会将携带这数据和页面名称的ModelAndView类对象返回给请求调度器
- 请求调度器收到ModelAndView对象后对其进行渲染
<!--视图解析器:DispatcherServlet给他的ModelAndView-->
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver" id="InternalResourceViewResolver">
<!--前缀-->
<property name="prefix" value="/WEB-INF/jsp/"/>
<!--后缀-->
<property name="suffix" value=".jsp"/>
</bean>
这里的web.xml配置会对ModelAndView对象中的视图名称进行处理,具体操作就是以/WEB-INF/jsp/加视图名称加.jsp的方式拼接出完整的url地址,并将ModelAndView对象携带的数据根据这个url地址传递给前端页面
- 前端页面接收数据并展示给用户
<body>
${msg}
</body>
简要分析执行流程
- DispatcherServlet表示前置控制器,是整个SpringMVC的控制中心。用户发出请求,DispatcherServlet接收请求并拦截请求。
我们假设请求的url为:
http://localhost:8080/SpringMVC/hello
如上url拆分成三部分:
http://localhost:8080服务器域名
SpringMVC部署在服务器上的web站点
hello表示控制器
通过分析,如上url表示为:请求位于服务器localhost:8080上的SpringMVC站点的hello控制器。
- HandlerMapping为处理器映射。DispatcherServlet调用HandlerMapping,HandlerMapping根据请求url查找Handler。
- HandlerExecution表示具体的Handler,其主要作用是根据url查找控制器,如上url被查找控制器为:hello。
- HandlerExecution将解析后的信息传递给DispatcherServlet,如解析控制器映射等。
- HandlerAdapter表示处理器适配器,其按照特定的规则去执行Handler。
- Handler让具体的Controller执行。
- Controller将具体的执行信息返回给HandlerAdapter,如ModelAndView。
- HandlerAdapter将视图逻辑名或模型传递给DispatcherServlet。
- DispatcherServlet调用视图解析器(ViewResolver)来解析HandlerAdapter传递的逻辑视图名。
- 视图解析器将解析的逻辑视图名传给DispatcherServlet。
- DispatcherServlet根据视图解析器解析的视图结果,调用具体的视图。
12、最终视图呈现给用户。
3.4 使用注解开发SpringMVC
在实际开发中,并不会像前面那样挨个进行配置,而是统一使用注解进行开发
注解实现
例:
springmvc-servlet.xml
<!--指定需要扫描的软件包,指定包下的注解会自动生效-->
<context:component-scan base-package="com.OCTDN.controller"/>
<!--加载注解驱动-->
<context:annotation-config/>
<!--设置SpringMVC不处理静态资源-->
<mvc:default-servlet-handler/>
<!--设置SpringMVC支持注解驱动
省去了配置BeanNameUrlHandlerMapping和SimpleControllerHandlerAdapter
-->
<mvc:annotation-driven/>
HelloController
//相当于注册Bean
@Controller
public class HelloController {
//Controller的真实访问地址,相当于Bean的id
@RequestMapping("/hello")
public String hello(Model model){
model.addAttribute("msg","SpringMVC!");
//返回视图解析器名,相当于/WEB-INF/jsp/hello.jsp
return "hello";
}
}
RestFul风格(重点!!!!!!!!!!!!!)
RestFul风格是一种资源定位及资源操作的风格,优点是能让软件更有层次,更安全,更易于实现和缓存
功能:
资源:互联网的所有事物都可以抽象为资源
资源操作:POST、DELETE、PUT、GET
分别对应添加、删除、修改、查询
传统资源操作方式(不同的方法实现不同的效果)
http://127.0.0.1/item/queryItem.action?id=1
查询,GET
http://127.0.0.1/item/saveItem.action
新增,POST
http://127.0.0.1/item/updateItem.action
更新,POST
http://127.0.0.1/item/deleteItem.action?id=1
删除,GET或POST
RestFul风格
可以通过不同的请求方式来实现不同的效果!
http://127.0.0.1/item/1
查询,GET
http://127.0.0.1/item
新增,POST
http://127.0.0.1/item
更新,PUT
http://127.0.0.1/item/1
删除,DELETE
例:
RestFulController
//以往:
// http://localhost:8080/Controller_war_exploded/add?a=1&b=10
//现在:http://localhost:8080/Controller_war_exploded/add/1/10
//@PathVariable:路径变量,根据变量名将URL传递的值传递给对应参数
///add/{a}/{b}:在URL中接收a和b的值
//method = RequestMethod.GET:接收GET方法传递的参数(网页默认都为GET)
@RequestMapping(value = "/add/{a}/{b}",method = RequestMethod.GET)
public String restful1(@PathVariable int a, @PathVariable int b, Model model) {
model.addAttribute("msg", a + b);
return "hello";
}
//PostMapping:接收POST方法传递的参数
@PostMapping("/add/{a}/{b}")
public String restful2(@PathVariable int a, @PathVariable int b, Model model) {
model.addAttribute("msg", a + b + "POST");
return "hello";
}
test.jsp
<form action="/add/1/50" method="post">
<input type="submit">
</form>
URL可能相同,但展示的内容不一定相同
重定向和请求转发
视图解析器默认为请求转发:return "hello";
若要重定向:return "redirect:/index.jsp";
数据处理
1、提交的域名称和处理方法的参数名一致
提交数据 : http://localhost:8080/user?name=OCTDN
@RequestMapping("/user")
public String user(String name, Model model){
//接收前端参数
System.out.println("前端参数为:" + name);
//将结果返回给前端
model.addAttribute("msg",name);
return "hello";
}
2、提交的域名称和处理方法的参数名不一致
提交数据 : http://localhost:8080/user/t2?username=OCTDN
@RequestMapping("/user/t2")
//@RequestParam:将从前端获取的参数传递给后面的参数
//例:username的值会传递给name
public String user2(@RequestParam("username") String name, Model model){
//接收前端参数
System.out.println("前端参数为:" + name);
//将结果返回给前端
model.addAttribute("msg",name);
return "hello";
}
3、提交的是一个对象
要求提交的表单域和对象的属性名一致,参数使用对象即可,如果使用对象的话,前端传递的参数名和对象名必须一致,否则就是null
提交数据 : http://localhost:8080/user/t3?name=OCTDN&id=1&age=15
@RequestMapping("/user/t3")
public String user3(User user){
System.out.println(user);
return "hello";
}
数据显示到前端的三种方式
1、ModelAndView(原始方式,基本不用)
2、Model(注解方式,最常用)
3、ModelMap(注解Plus版,很少用)
中文乱码问题
加个过滤器即可解决
- 自定义过滤器
FilterController
public class FilterController implements Filter {
@Override
public void init(FilterConfig filterConfig) throws ServletException {
Filter.super.init(filterConfig);
}
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException{
servletRequest.setCharacterEncoding("UTF-8");
servletResponse.setCharacterEncoding("UTF-8");
filterChain.doFilter(servletRequest,servletResponse);}
@Override
public void destroy() {
Filter.super.destroy();}
- 使用Spring的过滤器(配置即可使用)
<filter>
<filter-name>encodingSpring</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>
</filter>
<filter-mapping>
<filter-name>encodingSpring</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
3.5 JSON(重点!!!!!!!!!!!)
什么是JSON
JSON(JavaScript Object Notation, JS 对象标记) 是一种轻量级的数据交换格式,目前使用特别广泛。
采用完全独立于编程语言的文本格式来存储和表示数据。
简洁和清晰的层次结构使得 JSON 成为理想的数据交换语言。
易于人阅读和编写,同时也易于机器解析和生成,并有效地提升网络传输效率。
在JavaScript语言中,一切都是对象。因此,任何JavaScript 支持的类型都可以通过JSON来表示,例如字符串、数字、对象、数组等。看看他的要求和语法格式:
对象表示为键值对,数据由逗号分隔
花括号保存对象
方括号保存数组
JSON键值对是用来保存JavaScript对象的一种方式,键/值对组合中的键名写在前面并用双引号 "" 包裹,使用冒号 : 分隔,然后紧接着值:
例:
{"name":"OCTDN"}
{"age":"25"}
{"sex":"男"}
JSON其实是JavaScript对象的字符串表示法,使用文本表示一个JS对象,本质是一个字符串
前后端分离
后端部署接口(如:Controller)供前端访问
JSON实现前后端数据交互
前端独立部署,负责渲染后端提供的数据
JSON本质是一种数据交互的格式
JSON例子
jsonTest.html
<!--JavaScript脚本-->
<script type="text/javascript">
var user = {
name : "OCTDN",
age : 25,
sex : "男"
};
//将JS对象转换为JSON字符串
var json1 = JSON.stringify(user);
console.log(json1);
//将JSON字符串转换为JS对象
var json2 = JSON.parse(json1);
console.log(json2);
</script>
浏览该页面时可通过F12来查看输出结果
Jackson使用
Jackson是目前较好的JSON解析工具,能让Controller返回JSON数据
- 导入Maven依赖
<!--Jackson-->
<!-- https://mvnrepository.com/artifact/com.fasterxml.jackson.core/jackson-databind -->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.13.0-rc1</version>
</dependency>
- UserController
@Controller
public class UserController {
//这里需要给@RequestMapping设置编码格式,否则会乱码
@RequestMapping(value = "/json1",produces = "application/json;charset=utf-8")
//@ResponseBody会将对象以JSON格式返回给前端,
// 并且不会走视图解析器,而是返回一个JSON字符串
@ResponseBody
public String json1() throws JsonProcessingException {
//创建一个Jackson的对象映射器,用来解析数据
ObjectMapper objectMapper = new ObjectMapper();
//创建一个对象
User user = new User(1,"OCTDN",25);
//将对象解析为JSON格式
String s = objectMapper.writeValueAsString(user);
//将对象以JSON格式返回给前端
return s;}}
访问/json1地址即可查看输出结果
统一解决中文乱码问题
直接在SpringMVC注解驱动中配置即可
<!--JSON字符串中文编码转换-->
<mvc:annotation-driven>
<mvc:message-converters>
<bean class="org.springframework.http.converter.StringHttpMessageConverter">
<constructor-arg value="UTF-8"/>
</bean>
<bean class="org.springframework.http.converter.json.MappingJackson2HttpMessageConverter">
<property name="objectMapper">
<bean class="org.springframework.http.converter.json.Jackson2ObjectMapperFactoryBean">
<property name="failOnEmptyBeans" value="false"/>
</bean>
</property>
</bean>
</mvc:message-converters>
</mvc:annotation-driven>
若Controller中存在多个方法需要返回JSON字符串,则
@ResponseBody注解就显得非常麻烦,可直接在Controller上使用@RestController注解,该类就只会返回JSON字符串了
3.6 拦截器
什么是拦截器
过滤器
过滤器是servlet规范中的一部分,任何java web工程都可以使用,只需要在web.xml中进行配置,并在url-pattern中配置了/*之后,可以对所有要访问的资源进行拦截
拦截器
SpringMVC的拦截器只有在SpringMVC框架中才能使用,并且只会拦截访问Controller的方法,拦截器自带静态资源过滤功能
拦截器和过滤器的区别:拦截器是AOP思想的具体应用
编写拦截器
拦截器类需要实现HandlerInterceptor接口
自定义拦截器(例)
Interceptor
public class Interceptor implements HandlerInterceptor {
//拦截前
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
System.out.println("拦截器执行前");
/*
* true:可执行下一个拦截器,放行
* false:不执行下一个拦截器,不放行
* */
return true;
}
//拦截后,一般用来增加拦截日志
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
System.out.println("拦截器执行后");
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
System.out.println("拦截器被清理");
}
}
spring-mvc.xml
<!--配置拦截器-->
<mvc:interceptors>
<mvc:interceptor>
<!--
/:当前请求
/*:当前请求以及第一个子请求
/**:当前请求以及所有子请求
-->
<mvc:mapping path="/**"/>
<bean class="com.OCTDN.interceptor.Interceptor"/>
</mvc:interceptor>
</mvc:interceptors>
3.7 文件上传与下载
文件上传是项目开发中最常见的功能之一 ,springMVC 可以很好的支持文件上传,但是SpringMVC上下文中默认没有装配MultipartResolver,因此默认情况下其不能处理文件上传工作。如果想使用Spring的文件上传功能,则需要在上下文中配置MultipartResolver。
前端表单要求:为了能上传文件,必须将表单的method设置为POST,并将enctype设置为multipart/form-data。只有在这样的情况下,浏览器才会把用户选择的文件以二进制数据发送给服务器;
enctype属性
application/x-www=form-urlencoded:默认方式,只处理表单域中的 value 属性值,采用这种编码方式的表单会将表单域中的值处理成 URL 编码方式。
multipart/form-data:这种编码方式会以二进制流的方式来处理表单数据,这种编码方式会把文件域指定文件的内容也封装到请求参数中,不会对字符编码。
text/plain:除了把空格转换为 "+" 号外,其他字符都不做编码处理,这种方式适用直接通过表单发送邮件。
<%--enctype="multipart/form-data":文件上传必须要有这个--%>
<form action="${pageContext.request.contextPath}/upload" method="post" enctype="multipart/form-data">
<p>
<input type="file" name="file">
</p>
<p>
<input type="submit" value="上传">
</p>
</form>
SpringMVC实现文件上传
导入Maven依赖
<!--文件上传-->
<!-- https://mvnrepository.com/artifact/commons-fileupload/commons-fileupload -->
<dependency>
<groupId>commons-fileupload</groupId>
<artifactId>commons-fileupload</artifactId>
<version>1.4</version>
</dependency>
方式一:
spring-mvc.xml
<!--文件上传-->
<bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
<!--请求的编码格式,必须和JSP的pageEncoding相同-->
<property name="defaultEncoding" value="utf-8"/>
<!--文件大小上限-->
<property name="maxUploadSize" value="10485760"/>
<property name="maxInMemorySize" value="40960"/>
</bean>
【注】这个bena的id必须为:multipartResolver , 否则上传文件会报400的错误!
FileController
@Controller
public class FileController {
@RequestMapping("/upload")
/*@RequestParam("file"):给发起的请求携带的参数起别名
*CommonsMultipartFile file将接受name为file的文件
* 原理是将类型为file的表单控件封装为CommonsMultipartFile对象
* */
public String upload(@RequestParam("file") CommonsMultipartFile file, HttpServletRequest request) throws IOException {
//获取文件名
String originalFilename = file.getOriginalFilename();
//如果没有接受到(文件名为空),则返回首页
if ("".equals(originalFilename)){
return "redirect:/index.jsp";
}
//文件上传的保存路径
String path = request.getServletContext().getRealPath("/upload");
//如果路径不存在,则创建一个
File realPath = new File(path);
if (!realPath.exists()){
//xxx.mkdir():创建由xxx指定的目录
realPath.mkdir();
}
//获取文件输入流(用于上传)
InputStream inputStream = file.getInputStream();
//创建文件输出流(用于保存)
FileOutputStream fileOutputStream = new FileOutputStream(new File(realPath, originalFilename));
//文件读取和写出
int len = 0;
//创建缓冲流数组,输入流的数据会先读取到这里暂存
byte[] buffer = new byte[1024];
/*inputStream.read(buffer)
* 将输入流中的数据存到buffer中,并返回其字节数
* */
while ((len=inputStream.read(buffer))!=-1){
/*buffer:从缓冲区要写的数据
* 0:从第一个位置开始写
* len:要写的字节数长度
* */
fileOutputStream.write(buffer,0,len);
//强制将数据输出干净
fileOutputStream.flush();
}
fileOutputStream.close();
inputStream.close();
return "redirect:/index.jsp";
}
}
方式二
使用file.Transto实现
@RequestMapping("/upload2")
public String fileUpload2(@RequestParam("file") CommonsMultipartFile file, HttpServletRequest request) throws IOException {
//上传路径保存设置
String path = request.getServletContext().getRealPath("/upload");
File realPath = new File(path);
if (!realPath.exists()){
realPath.mkdir();
}
//上传文件地址
System.out.println("上传文件保存地址:"+realPath);
//通过CommonsMultipartFile的方法直接写文件 file.transferTo(new File(realPath +"/"+ file.getOriginalFilename()));
return "redirect:/index.jsp";
}
文件下载
FileController
@RequestMapping("/download")
public String download(HttpServletRequest request, HttpServletResponse response) throws IOException {
//目标文件的地址
String path = request.getServletContext().getRealPath("/upload");
//文件名,这里因为是案例所以直接直接写死
String fileName = "adit-alfian-artorias-the-abbyswalker.jpg";
//设置response响应头,告诉浏览器如何处理response响应
//设置页面不缓存,清空buffer
response.reset();
//设置字符编码
response.setCharacterEncoding("UTF-8");
//设置以二进制传输数据
response.setContentType("multipart/form-data");
//设置response响应头
response.setHeader("Content-Disposition","attachment;fileName="+ URLEncoder.encode(fileName, "UTF-8"));
//创建File对象,参数为文件路径和文件名
File file = new File(path,fileName);
//创建文件输入流来读取文件
FileInputStream fileInputStream = new FileInputStream(file);
//获取输出流来下载文件
ServletOutputStream outputStream = response.getOutputStream();
//创建数据缓冲区数组
byte[] buffer = new byte[1024];
int len = 0;
//写出数据
/*inputStream.read(buffer)
* 将输入流中的数据存到buffer中,并返回其字节数
* */
while ((len=fileInputStream.read(buffer))!=-1){
/*buffer:从缓冲区要写的数据
* 0:从第一个位置开始写
* len:要写的字节数长度
* */
outputStream.write(buffer,0,len);
//强制将数据输出干净
outputStream.flush();
}
outputStream.close();
fileInputStream.close();
return null;
}
index.jsp
<a href="${pageContext.request.contextPath}/download">下载</a>
- MyBatis框架
4.1 什么是Mybatis(SSM)
MyBatis 是一款优秀的持久层框架,它支持定制化 SQL、存储过程以及高级映射。MyBatis 避免了几乎所有的 JDBC 代码和手动设置参数以及获取结果集。MyBatis 可以使用简单的 XML 或注解来配置和映射原生信息,将接口和 Java 的 POJO(Plain Ordinary Java Object,普通的 Java对象)映射成数据库中的记录。
获取Mybatis
- Maven仓库:
https://mvnrepository.com/artifact/org.mybatis/mybatis
- Github:
https://github.com/mybatis/mybatis-3/releases
- 中文文档:
https://mybatis.org/mybatis-3/zh/index.html
什么是持久化
持久化:将程序的数据在持久状态和瞬时状态转化的过程
持久状态:存入数据库中
什么是持久层
持久层包括三种基本层:
- Dao层:Data Access Object,数据库访问对象
- Service层:业务层,使用一个或多个模型执行操作
- Controller层:调用Service层的接口来控制业务流程
持久层:完成持久化工作的代码块
【注】:层的界限是非常明确的,一个项目中每一层的功能都是独一且专一的
为什么需要MyBatis
极大地简化了传统JDBC代码,帮助程序员更加便捷的将数据存入数据库中(不嫌麻烦的话也可以不使用哦)
优缺点
优点:
- 简单易学,灵活
- SQL和代码的分离,提高了可维护性
- 提供映射标签,支持对象与数据库的ORM字段关系映射
- 提供对象关系映射标签,支持对象关系组件维护
- 提供xml标签,支持编写动态SQL
- 用的人太多了,不会的话会找不到工作的!!!!!!!!
4.2 第一个Mybatis程序
搭建环境
(1)搭建数据库:这还用教?????
(2)新建普通Maven项目并连接数据库:这还用教?????(记得删除src文件夹)
(3)导入相关的jar包
<dependencies>
<!--mysql-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.25</version>
</dependency>
<!--mybatis-->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.5.7</version>
</dependency>
<!--junit-->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.13.2</version>
</dependency>
<!--在<build>中配置resources,来避免我们资源导出失败的问题-->
<build>
<resources>
<resource>
<directory>src/main/resources</directory>
<includes>
<include>**/*.properties</include>
<include>**/*.xml</include>
</includes>
<filtering>false</filtering>
</resource>
<resource>
<directory>src/main/java</directory>
<includes>
<include>**/*.properties</include>
<include>**/*.xml</include>
</includes>
<filtering>false</filtering>
</resource>
</resources>
</build>>
创建模块
(1)创建一个mybatis子模块:这还用教?????
(2)编写Mybatis的核心配置文件:
在新建的模块中的src-main-resources文件夹内新建一个
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/smbms?useSSL=true&useUnicode=true&characterEncoding=UTF-8"/>
<property name="username" value="root"/>
<property name="password" value="964878"/>
</dataSource>
</environment>
</environments>
<!--每一个Mapper.xml都需要在Mybatis核心配置文件中注册才能使用-->
<mappers>
<!--Mapper.xml注册-->
</mappers>
</configuration>
- 编写Mybatis的工具类:
每个基于 MyBatis 的应用都是以一个 SqlSessionFactory 的实例为核心的。
既然有了 SqlSessionFactory,顾名思义,我们可以从中获得 SqlSession 的实例。SqlSession 提供了在数据库执行 SQL 命令所需的所有方法。你可以通过 SqlSession 实例来直接执行已映射的 SQL 语句。
//Mybatis工具类
public class MybatisUtils {
private static SqlSessionFactory sqlSessionFactory;
//静态代码块,类加载时将自动初始化内部的代码
static {
try {
//获取sqlSessionFactory对象:sqlSession
String resource = "mybatis-config.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
} catch (IOException e) {
e.printStackTrace();
}
}
public static SqlSession getSqlSession() {
SqlSession sqlSession = sqlSessionFactory.openSession();
return sqlSession;
}
}
编写MyBatis代码
(1)实体类:这还用教?????
(2)Dao接口
public interface UserDao {
List<User> getUserList();
}
(3)接口实现类
由于Mybatis简化了所有JDBC的代码,所以不再需要创建XXXXImpl.java了,改为了创建XXXXMapper.xml配置文件
【注】在Mybatis中,一个sql语句既可以通过 XML 定义,也可以通过注解定义,然后通过SqlSession调用即可
<?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(命名空间)=“绑定一个Dao/Mapper接口”-->
<mapper namespace="com.OCTDN.dao.UserDao">
<!--select查询语句,id对应接口里的方法名,reslutType对应结果集类型-->
<select id="getUserList" resultType="com.OCTDN.pojo.User">
select * from mybatis.user
</select>
</mapper>
测试
(1)在核心配置文件中注册mappers
<mappers>
<mapper resource="com/OCTDN/dao/UserMapper.xml"/>
</mappers>
- 编写测试类并运行
例:
public class UserDaoTest {
@Test
public void Test(){
SqlSession sqlSession = null;
try{
//获取SqlSession对象
sqlSession = MybatisUtils.getSqlSession();
//通过获取UserDao这个接口类来获取UserMapper中的<mapper>
UserDao mapper = sqlSession.getMapper(UserDao.class);
//执行SQL
List<User> userList = mapper.getUserList();
for (User user:userList){
System.out.println(user);
}
} catch (Exception e){
e.printStackTrace();
} finally {
//关闭SqlSession
sqlSession.close();
}
}
}
4.3 CRUD
增加(Create)
检索(Retrieve)
更新(Update)
删除(Delete)
namespace(命名空间)
namespace中的包名必须和Dao/Mapper接口名一致!!!!
select(查询)
id:对应namespace中的方法名,如getUserList
resultType:返回值类型
parameterType:参数类型
例:
根据id查询用户
UserDao:
//根据ID查询用户
List<User> getUserIdList(int id);
UserMapper.xml
<select id="getUserIdList" resultType="com.OCTDN.pojo.User" parameterType="int">
select * from mybatis.user where id=#{id}
</select>
添加用户
UserDao
//增加用户
int addUser(User user);
UserMapper.xml
<insert id="addUser" parameterType="com.OCTDN.pojo.User">
insert into mybatis.user(id, name, password, age, gender, phone, address, birthday)
VALUE (#{id},#{name},#{password},#{age},#{gender},#{phone},#{address},#{birthday})
</insert>
UserDaoTest
int user = mapper.addUser(new User(4, "CCCCC", "333333", 30, 1, "33333333333", "自己家", new Date()));
//增、删、改都需要【提交事务】!!!!!
sqlSession.commit();
修改用户
UserDao
//修改用户
int updateUser(User user);
UserMapper.xml
<update id="updateUser" parameterType="com.OCTDN.pojo.User">
update mybatis.user
set name = #{name},password = #{password},age = #{age},gender = #{gender},phone = #{phone},address = #{address},birthday = #{birthday}
where id = ${id};
</update>
UserDaoTest
int user = mapper.updateUser(new User(3, "CCCCC", "333333", 30, 1, "33333333333", "自己家", new Date()));
//增、删、改都需要【提交事务】!!!!!
sqlSession.commit();
删除用户
UserDao
//删除用户
int deleteUser(int id);
UserMapper.xml
<!--int类型的parameterType可以不写-->
<delete id="deleteUser" parameterType="int">
delete
from mybatis.user
where id = ${id};
</delete>
UserDaoTest
int user = mapper.deleteUser(4);
//增、删、改都需要【提交事务】!!!!!
sqlSession.commit();
【注】当表中有大量数据且都为非空时,可使用Map来修改或查询表中指定数据,可避免未修改一项数据而需要设置其余大量数据的情况出现(这是个野路子,不正规,但特定情况下有用)
例:使用Map来修改User表中的个别数据
UserDao
//修改用户(Map版)
int updateUserMap(Map<String, Object> map);
UserMapper.xml
<!--Map版修改用户-->
<update id="updateUserMap" parameterType="map">
update mybatis.user
set name =#{username},password = #{userpwd}
where id = ${userid};
</update>
UserDaoTest
Map<String, Object> map = new HashMap<>();
map.put("userid",2);
map.put("username","xxxxx");
map.put("userpwd","XXXXX");
mapper.updateUserMap(map);
//增、删、改一定要记得开启事务
sqlSession.commit();
【注】众所周知,SQL模糊查询存在注入问题,为了避免注入问题,在模糊查询时需要将SQL写为以下样式
select * from mybatis.user like "%"#{param}"%"
也可在调用方法传递参数时写为以下样式
List<User> userList = mapper.getUserList("%param%");
4.4 Mybatis配置解析
MyBatis 的配置文件包含了会深深影响 MyBatis 行为的设置和属性信息。
核心配置文件:mybatis-config.xml
configuration(配置){
properties(属性)
setting(设置)
typeAliases(类型别名)----------------了解即可
typeHandiers(类型处理器)-------------了解即可
objectFactory(对象工厂)--------------了解即可
plugins(插件)
environments(环境配置){
environment(环境变量)
transactionManager(事务管理器)
dataSource(数据源)
}
databaseldProvider(数据库厂商标识)---了解即可
mappers(映射器)
}
【注】Mybatis配置必须严格按照上述顺序配置!!!!!
environments(环境配置)
MyBatis 可以配置成适应多种环境,这种机制有助于将 SQL 映射应用于多种数据库之中, 现实情况下有多种理由需要这么做。
不过要记住:尽管可以配置多个环境,但每个 SqlSessionFactory 实例只能选择一种环境。
【注】如果需要切换环境配置,只需要将
<environments default="development">
中default的值换成指定的
<environment id="xxxx">的id值即可
transactionManager(事务管理器)
在 MyBatis 中有两种类型的事务管理器(也就是 type="[JDBC|MANAGED]"):
JDBC(默认):这个配置直接使用了 JDBC 的提交和回滚设施,它依赖从数据源获得的连接来管理事务作用域。
MANAGED:这个配置几乎没做什么。默认情况下它会关闭连接。然而一些容器并不希望连接被关闭,因此需要将 closeConnection 属性设置为 false 来阻止默认的关闭行为。
【注】如果你正在使用 Spring + MyBatis,则没有必要配置事务管理器,因为 Spring 模块会使用自带的管理器来覆盖前面的配置。
dataSource(数据源)了解即可,太多了
dataSource元素使用标准的 JDBC 数据源接口来配置 JDBC 连接对象的资源。
大多数 MyBatis 应用程序会按示例中的例子来配置数据源。虽然数据源配置是可选的,但如果要启用延迟加载特性,就必须配置数据源。
有三种内建的数据源类型(也就是 type="[UNPOOLED|POOLED|JNDI]"):
- UNPOOLED(没有池)
- POOLED(有池,默认)
- JNDI(常规)
POOLED
这种数据源的实现利用“池”的概念将 JDBC 连接对象组织起来,避免了创建新的连接实例时所必需的初始化和认证时间。 这种处理方式很流行,能使并发 Web 应用快速响应请求。
除了上述提到 UNPOOLED 下的属性外,还有更多属性用来配置 POOLED 的数据源:
- poolMaximumActiveConnections – 在任意时间可存在的活动(正在使用)连接数量,默认值:10
- poolMaximumIdleConnections – 任意时间可能存在的空闲连接数。
- poolMaximumCheckoutTime – 在被强制返回之前,池中连接被检出(checked out)时间,默认值:20000 毫秒(即 20 秒)
- poolTimeToWait – 这是一个底层设置,如果获取连接花费了相当长的时间,连接池会打印状态日志并重新尝试获取一个连接(避免在误配置的情况下一直失败且不打印日志),默认值:20000 毫秒(即 20 秒)。
- poolMaximumLocalBadConnectionTolerance – 这是一个关于坏连接容忍度的底层设置, 作用于每一个尝试从缓存池获取连接的线程。 如果这个线程获取到的是一个坏的连接,那么这个数据源允许这个线程尝试重新获取一个新的连接,但是这个重新尝试的次数不应该超过 poolMaximumIdleConnections 与 poolMaximumLocalBadConnectionTolerance 之和。 默认值:3(新增于 3.4.5)
- poolPingQuery – 发送到数据库的侦测查询,用来检验连接是否正常工作并准备接受请求。默认是“NO PING QUERY SET”,这会导致多数数据库驱动出错时返回恰当的错误消息。
- poolPingEnabled – 是否启用侦测查询。若开启,需要设置 poolPingQuery 属性为一个可执行的 SQL 语句(最好是一个速度非常快的 SQL 语句),默认值:false。
- poolPingConnectionsNotUsedFor – 配置 poolPingQuery 的频率。可以被设置为和数据库连接超时时间一样,来避免不必要的侦测,默认值:0(即所有连接每一时刻都被侦测 — 当然仅当 poolPingEnabled 为 true 时适用)。
properties(必须掌握)
这些属性可以在外部进行配置,并可以进行动态替换。你既可以在典型的 Java 属性文件中配置这些属性,也可以在 properties 元素的子元素中设置。(引入外部配置文件)
例:
db.properties
driver=com.mysql.jdbc.Driver
url=jdbc:mysql://localhost:3306/mybatis?useSSL=true&useUnicode=true&characterEncoding=UTF-8
username=root
password=xxxxxx
mybatis-config.xml
<!--引入外部配置文件-->
<properties resource="db.properties"/>
....................
<dataSource type="POOLED">
<property name="driver" value="${driver}"/>
<property name="url" value="${url}"/>
<property name="username" value="${username}"/>
<property name="password" value="${password}"/>
</dataSource>
也可在properties标签内增加一些属性:
db.properties
driver=com.mysql.jdbc.Driver
url=jdbc:mysql://localhost:3306/mybatis?useSSL=true&useUnicode=true&characterEncoding=UTF-8
mybatis-config.xml
<properties resource="db.properties">
<property name="username" value="root"/>
<property name="password" value="xxxxxxx"/>
</properties>
..........................
【注】属性会优先使用外部引入文件内的属性值
typeAliases(类型别名,必须掌握)
类型别名可为 Java 类型设置一个缩写名字。 它仅用于 XML 配置,意在降低冗余的全限定类名书写。
如(方法一,推荐实体类较少时使用):
<!--给实体类起别名,优化使用-->
<typeAliases>
<typeAlias alias="User" type="com.OCTDN.pojo.User"/>
</typeAliases>
也可制定一个包,Mybatis会在包下搜索需要的实体类并自动使用实体类的首字母小写的非限定类名来作为别名
如(方法二,推荐实体类较多时适用):
<typeAliases>
<package name="com.OCTDN.pojo"/>
</typeAliases>
UserMapper.xml
<select id="getUserList" resultType="user">
select * from mybatis.user
</select>
方法二也可在实体类中用注解来手动设置别名
如:
@Alias("user")
public class User {
setting(设置,必须掌握)
由于东西较多,推荐使用的时候到官网翻阅,用多了就记住了
mappers(映射器)
既然 MyBatis 的行为已经由上述元素配置完了,我们现在就要来定义 SQL 映射语句了。 但首先,我们需要告诉 MyBatis 到哪里去找到这些语句。 在自动查找资源方面,Java 并没有提供一个很好的解决方案,所以最好的办法是直接告诉 MyBatis 到哪里去找映射文件。 你可以使用相对于类路径的资源引用,或完全限定资源定位符(包括 file:/// 形式的 URL),或类名和包名等。
- 使用相对于类路径的资源引用(常规,推荐使用)
<mappers>
<mapper resource="com/OCTDN/dao/UserMapper.xml"/>
</mappers>
- 使用完全限定资源定位符(URL)(不常用)
<mappers>
<mapper url="jetbrains://idea/navigate/reference?project=Mybatis&path=com/OCTDN/dao/UserMapper.xml"/>
</mappers>
- 使用映射器接口实现类的完全限定类名
<mappers>
<mapper class="com.OCTDN.dao.UserDao"/>
</mappers>
- 将包内的映射器接口实现全部注册为映射器
<mappers>
<package name="com.OCTDN.dao"/>
</mappers>
【注】(3)和(4)的共同注意事项
- 接口和Mapper配置文件必须同名
- 接口和Mapper配置文件必须在同一个包下
生命周期和作用域
作用域和生命周期类别是至关重要的,因为错误的使用会导致非常严重的并发问题
SqlSessionFactoryBuilder
本质是创建一个SqlSessionFactory,一旦SqlSessionFactory被创建,SqlSessionFactoryBuilder就会立即失效
作用域:局部方法
SqlSessionFactory
本质是一个数据库连接对象,是创建SqlSession的工厂,SqlSessionFactory一旦被创建就会一直存在,直到整个程序停止运行,SqlSessionFactory相当于一个数据库连接池,但是每个程序中只能创建一个SqlSessionFactory,否则将挤占大量运行资源
作用域:全局(application,应用作用域)
SqlSession
本质是数据库连接池中的一个请求,也是事务执行者,所以使用前一定要记得开启事务(sqlSession.commit();)
SqlSession的实例不是线程安全的,是不能被共享的,所以使用完毕后一定要记得关闭(sqlSession.close();),否则将挤占大量运行资源
作用域:局部(请求或方法内,用前开启,用后关闭)
4.5 ResultMap
解决属性名与字段名不一致的问题
测试:
将User里的password改为pwd
结果:
简单暴力又笨的解决办法:别名
将对应SQL语句改为:
select id,name,pwd as password,.....
from mybatis.user
ResultMap(结果集映射)
将结果集中的字段映射到不同名的属性中
例:
<!--id:对应SQL方法中的resultMap,tyoe:对应目标实体类-->
<resultMap id="UserMap" type="User">
<!--column:数据库中的字段名,property:实体类中的属性名-->
<result column="password" property="pwd"/>
</resultMap>
<!--select查询语句,id对应接口里的方法名-->
<select id="getUserList" resultMap="UserMap">
select * from mybatis.user
</select>
resultMap 元素是 MyBatis 中最重要最强大的元素。它可以让你从 90% 的 JDBC ResultSets 数据提取代码中解放出来,并在一些情形下允许你进行一些 JDBC 不支持的操作。实际上,在为一些比如连接的复杂语句编写映射代码的时候,一份 resultMap 能够代替实现同等功能的数千行代码。ResultMap 的设计思想是,对简单的语句做到零配置,对于复杂一点的语句,只需要描述语句之间的关系就行了。
【WARNING】
ResultMap后面会有非常复杂的应用,会涉及到多个表之间如同蜘蛛网一般的复杂联系,请做好准备
4.6 日志
日志工厂
如果SQL语句有错,就需要排错,通常会使用sout输出SQL语句或使用Debug查看错误信息,但Mybatis提供了另一种工具来帮助开发者排错,即日志工厂,setting(设置)中的logImpl
logImpl :
指定 MyBatis 所用日志的具体实现,未指定时将自动查找。
有效值:
SLF4J
LOG4J(掌握)
LOG4J2
JDK_LOGGING:Java自带日志输出
COMMONS_LOGGING:工具包
STDOUT_LOGGING(掌握):控制台输出,也是标准日志输出
NO_LOGGING:没有日志输出
STDOUT_LOGGING
<settings>
<setting name="logImpl" value="STDOUT_LOGGING"/>
</settings>
测试:
根据ID查询用户
以下为输出的信息的有用的部分:
打开JDBC连接
Opening JDBC Connection
创建connection对象
Created connection 1436633036.
设置自动提交为false
Setting autocommit to false on JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@55a147cc]
预编译SQL
==> Preparing: select * from mybatis.user where id=?
传入的参数
==> Parameters: 2(Integer)
查询的字段
<== Columns: id, name, password, age, gender, phone, address, birthday
查询出的记录
<== Row: 2, xxxxx, XXXXX, 20, 2, 11111111111, 自己家, 2001-11-15
结果中的记录总数
<== Total: 1
查询出的结果
User{id=2, name='xxxxx', pwd='null', age=20, gender=2, phone='11111111111', address='自己家', birthday=Thu Nov 15 00:00:00 CST 2001}
设置结果集自动提交
Resetting autocommit to true on JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@55a147cc]
关闭connection
Closing JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@55a147cc]
将connection返回数据库连接池
Returned connection 1436633036 to pool.
LOG4J(重点)
什么是LOG4J
Log4j是Apache的一个开源项目,通过使用Log4j,我们可以控制日志信息输送的目的地是控制台、文件、GUI组件,甚至是套接口服务器、NT的事件记录器、UNIX Syslog守护进程等;我们也可以控制每一条日志的输出格式;通过定义每一条日志信息的级别,我们能够更加细致地控制日志的生成过程。这些可以通过一个配置文件来灵活地进行配置,而不需要修改应用的代码。
- 导入所需的jar包(或Maven依赖)
<!-- https://mvnrepository.com/artifact/log4j/log4j -->
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.17</version>
</dependency>
- LOG4J配置(log4j.properties)
花样非常多,并不是所有设置项都用得上,所以不要尝试去记,能看懂就行,用的时候根据自身情况去百度CV再微调就行了
本教程所使用的配置(用不上的都没有写):
#将等级为DEBUG的日志信息输出到console和file这两个目的地,console和file的定义在下面的代码
log4j.rootLogger=DEBUG,console,file
#控制台输出的相关设置
log4j.appender.console = org.apache.log4j.ConsoleAppender
log4j.appender.console.Target = System.out
log4j.appender.console.Threshold=DEBUG
log4j.appender.console.layout = org.apache.log4j.PatternLayout
log4j.appender.console.layout.ConversionPattern=[%c]-%m%n
#文件输出的相关设置
log4j.appender.file = org.apache.log4j.RollingFileAppender
log4j.appender.file.File=./log/OCTDN.log
log4j.appender.file.MaxFileSize=10mb
log4j.appender.file.Threshold=DEBUG
log4j.appender.file.layout=org.apache.log4j.PatternLayout
log4j.appender.file.layout.ConversionPattern=[%p][%d{yy-MM-dd}][%c]%m%n
#日志输出级别
log4j.logger.org.mybatis=DEBUG
log4j.logger.java.sql=DEBUG
log4j.logger.java.sql.Statement=DEBUG
log4j.logger.java.sql.ResultSet=DEBUG
log4j.logger.java.sql.PreparedStatement=DEBUG
- 设置日志为LOG4J
<settings>
<setting name="logImpl" value="LOG4J"/>
</settings>
设置日志对象为当前类
static Logger logger =
Logger.getLogger(当前类名.class);
【注】Logger需要导入的类为:org.apache.log4j.Logger
LOG4J日志级别:info、debug、error
4.7 分页
语法:
- 从第a条记录开始,每页显示b条
select * from mybatis.user limit a,b
2、显示X条记录
select * from mybatis.user limit X
使用Mybatis实现Limit分页
例:
UserDao
//Limit分页
List<User> getUserLimitList(Map<String,Integer> map);
UserMapper.xml
<select id="getUserLimitList" parameterType="map" resultMap="UserMap">
select * from mybatis.user limit #{startIndex},#{pageSize}
</select>
UserDaoTest
HashMap<String, Integer> map = new HashMap<>();
map.put("startIndex",0);
map.put("pageSize",2);
List<User> userList = mapper.getUserLimitList(map);
4.8 使用注解开发(了解即可)
直接在接口的方法上编写SQL即可
例:
UserDao:
//获取用户列表
@Select("select * from user")
List<User> getUserList();
mybatis-config.xml:(使用注解开发一定要绑定接口类)
<!--绑定接口-->
<mappers>
<mapper class="com.OCTDN.dao.UserDao"></mapper>
</mappers>
UserDaoTest:
不变
本质还是使用反射机制实现
【注】此方法只能在一些简单的SQL上,稍微负责一些的就不要使用了
使用注解实现CRUD
可在工具类创建的时候实现自动提交事务
在MybatisUtils中的getSqlSession()方法中的openSession()括号内添加参数true,即可开启事务自动提交
使用注解实现查询
UserDao
//根据ID查询用户
@Select("select * from user where id = #{userid}")
/*使用@Param注解来标记对应参数,
如果存在多个参数且都为基本数据类型时,
必须用@Param注解标记*/
User getUserById(@Param("userid") int id);
使用注解实现添加
UserDao
//注解增加用户
@Insert("insert into user(id,name,password) values (#{id},#{name},#{pwd})")
int addUser(User user);
UserDaoTest
int ccccc = mapper.addUser(new User(4, "CCCCC", "111111"));
//由于工具类中设置了事务自动提交,所以不再需要手动开启事务
sqlSession.close();
使用注解实现修改
UserDao
//使用注解实现修改
@Update("update user set name=#{name},password=#{pwd} where id=#{id}")
int updateUser(User user);
UserDaoTest
mapper.updateUser(new User(4, "FFFFF", "666666"));
//由于工具类中设置了事务自动提交,所以不再需要手动开启事务
sqlSession.close();
使用注解实现删除
UserDao
//使用注解实现删除
@Delete("delete from user where id=#{userid}")
int deleteUser(@Param("userid") int id);
UserDaoTest
mapper.deleteUser(4);
//由于工具类中设置了事务自动提交,所以不再需要手动开启事务
sqlSession.close();
【注】注解只能在一些简单的SQL上,稍微负责一些的就不要使用了
4.9 Lombok工具
【注】这是一个偷懒专用工具,请根据实际情况选择是否使用
总的来说就是在POJO实体类中,不用再编写getter/setter/toString等代码,只需要添加对应的注解即可
使用步骤:
1、在IDEA中安装Lombok插件
设置-插件-搜索-安装(有些版本的IDEA会默认已安装Lombok)
- 导入Maven依赖
<!-- https://mvnrepository.com/artifact/org.projectlombok/lombok -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.20</version>
</dependency>
Lombok注解
@Getter and @Setter:对应get和set方法
@FieldNameConstants:字段属性常量
@NoArgsConstructor:无参构造函数
@Data:数据
@AllArgsConstructor:全部构造函数
@ToString:toString
@EqualsAndHashCode
@RequiredArgsConstructor
@Log
@Log4j
@Log4j2
@Slf4j
@XSlf4j
@CommonsLog
@JBossLog
@Flogger
@CustomLog
@Builder
@SuperBuilder
@Singular
@Delegate
@Value
@Accessors
@Wither
@With
@SneakyThrows
@val
@var
@UtilityClass
@Data用的较多,会自动生成无惨构造函数、get、set、toString、hashcode、equals等等方法
以下三个注解即可完成常规实体类方法的编写
@Data
@AllArgsConstructor
@NoArgsConstructor
4.10 复杂SQL语句环境搭建(难点!!!)
环境搭建
(1)创建teacher和student表
【注】student数据库需要一个外键
将fktid与tid(外键)绑定
设置约束名为fktid,外键为tid,引用teacher表的id属性
CREATE TABLE `student`(
`id` INT(10) NOT NULL AUTO_INCREMENT,
`name` VARCHAR(30) NOT NULL,
`tid` INT(10) NOT NULL,
PRIMARY KEY(`id`),
KEY `fktid` (`tid`),
CONSTRAINT `fktid` FOREIGN KEY (`tid`) REFERENCES `teacher` (`id`)
)ENGINE=INNODB DEFAULT CHARSET=utf8;
架构
(2)Student和Teacher实体类
Student
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Student {
private int id;
private String name;
//每个学生都要关联一个老师
private Teacher teacher;
}
Teacher
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Teacher {
private int id;
private String name;
}
(3)接口以及配置文件
StudentMapper接口和xml配置文件
TeacherMapper接口和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(命名空间)=“绑定一个Dao/Mapper接口”-->
<mapper namespace="com.OCTDN.dao.StudentMapper">
</mapper>
- 注册Mapper
<mappers>
<mapper resource="com/OCTDN/dao/TeacherMapper.xml"></mapper>
<mapper resource="com/OCTDN/dao/StudentMapper.xml"></mapper>
</mappers>
- 测试:查询Teacher表
TeacherMapper
@Select("select * from teacher where id = #{teaid}")
Teacher getTeacherById(@Param("teaid") int id);
StuTeaTest
@Test
public void Test(){
SqlSession sqlSession = MybatisUtils.getSqlSession();
TeacherMapper mapper = sqlSession.getMapper(TeacherMapper.class);
Teacher teacher = mapper.getTeacherById(1);
System.out.println(teacher);
sqlSession.close();
}
多对一(对象要使用association关联)
由于SQL语句复杂,所以不能使用注解
例:查询所有的学生以及对应的老师信息
按照查询嵌套处理
StudentMapper
//查询所有的学生以及对应的老师信息
public List<Student> getStudent();
StudentMapper.xml
<!--查询所有学生,结果集映射为StudentTeacher-->
<select id="getStudent" resultMap="StudentTeacher">
select * from mybatis.student
</select>
<!--结果集映射为Student-->
<resultMap id="StudentTeacher" type="Student">
<result property="id" column="id"></result>
<result property="name" column="name"></result>
<!--对象要使用association关联,teacher对象属性对应tid字段
由于Student类包含一个Teacher类对象,所以需要增加一个javaType="Teacher"
嵌套getTeacher查询方法,外键tid对应teacher表的id-->
<association property="teacher" column="tid" javaType="Teacher" select="getTeacher"></association>
</resultMap>
<!--根据ID查询老师-->
<select id="getTeacher" resultType="Teacher">
select * from mybatis.teacher where id = ${id}
</select>
StuTeaTest
List<Student> student = mapper.getStudent();
for (Student student1 : student) {
System.out.println(student1);
}
sqlSession.close();
首先查询学生表,然后查询教师表,通过外键将结果关联起来
按照结果嵌套处理
StudentMapper
//按照结果嵌套处理多对一问题
public List<Student> getStudent2();
StudentMapper.xml
<!--按照结果嵌套处理多对一问题-->
<select id="getStudent2" resultMap="StudentTeacher2">
select s.id sid,s.name sname,t.name tname
from mybatis.student s,mybatis.teacher t
where s.tid = t.id
</select>
<resultMap id="StudentTeacher2" type="Student">
<result property="id" column="sid"></result>
<result property="name" column="sname"></result>
<!--结果集中,teacher是一个Teacher类对象-->
<association property="teacher" javaType="Teacher">
<!--teacher对象里包含一个name属性,对应tname字段-->
<result property="name" column="tname"></result>
</association>
</resultMap>
一对多(集合要使用collection)
由于SQL语句复杂,所以不能使用注解
例:查询指定的教师以及对应的学生信息
按照结果嵌套处理
TeacherMapper
//查询指定教师的信息以及对应学生的信息
public List<Teacher> getTeacher(@Param("tid")int id);
TeacherMapper.xml
<!--通过结果嵌套实现一对多-->
<select id="getTeacher" resultMap="TeacherStudent">
select t.id teaid,t.name teaname,s.id stuid,s.name stuname
from mybatis.student s,mybatis.teacher t
where s.tid = t.id and t.id = #{tid}
</select>
<resultMap id="TeacherStudent" type="Teacher">
<result property="id" column="teaid"></result>
<result property="name" column="teaname"></result>
<!--由于一对多属于集合,所以要使用collection
由于students属于泛型集合,所以要使用ofType来指定泛型的类型
-->
<collection property="students" ofType="Student">
<result property="id" column="stuid"></result>
<result property="name" column="stuname"></result>
<result property="tid" column="tid"></result>
</collection>
</resultMap>
总结:
多个数据对应一个对象属于关联,要使用association,要声明对象名和对应的字段以及对象的类型
一个对象对应多个数据属于集合,要使用collection,要声明集合名和集合的泛型类,也要声明集合内的属性以及对应的字段
本质就是子查询和联表查询
4.11 动态SQL
可根据不同弄的情况生成不同的的SQL语句
环境搭建
在utils软件包里新增一个IDutils
//生成随机ID
public class IDutils {
public static String getId(){
return UUID.randomUUID().toString().replaceAll("-","");
}
}
创建Bolg表
CREATE TABLE `blog`(
`id` VARCHAR(50) NOT NULL,
`title` VARCHAR(100) NOT NULL,
`author` VARCHAR(30) NOT NULL,
`create_time` DATETIME NOT NULL,
`views` INT(30) NOT NULL,
PRIMARY KEY(`id`)
)ENGINE=INNODB DEFAULT CHARSET=utf8;
创建BolgMapper和对应的xml配置文件
if语句
BlogMapper
//if动态查询Blog
List<Blog> queryBlogByIf(Map map);
BlogMapper.xml
<!--if动态查询Blog-->
<select id="queryBlogByIf" parameterType="map" resultType="Blog">
select * from mybatis.blog where 1=1
<if test="title != null">
and title = #{title}
</if>
<if test="author != null">
and author = #{author}
</if>
</select>
BlogTest
Map blogMap = new HashMap();
blogMap.put("author","OCTDN");
List<Blog> blogs = mapper.queryBlogByIf(blogMap);
for (Blog blog : blogs) {
System.out.println(blog);
}
choose,when,otherwise(类似switch/case)
同switch/case一样,只要有一个条件满足,就不会执行后面的语句
BlogMapper
//choose,when,otherwise动态查询Blog
List<Blog> queryBlogByChoose(Map map);
BlogMapper.xml
<!--choose,when,otherwise动态查询Blog-->
<select id="queryBlogByChoose" parameterType="map" resultType="Blog">
select * from mybatis.blog where 1=1
<choose>
<when test="title != null">
and title = #{title}
</when>
<when test="author != null">
and author = #{author}
</when>
<otherwise>
and views = #{views}
</otherwise>
</choose>
</select>
where、set
在前两种例子中存在一段非常不严谨的SQL语句:
where 1=1
where后面的判断条件也应该是动态的,加入<where>标签即可实现
例:
<where>
<if>或<choose>
</where>
只有在<where>标签内至少有一个判断条件成立的情况下,<where>标签才会生效,且<where>标签会自动将第一个成立的SQL语句中的and或or去掉以便成功拼接SQL语句
<set>标签会自动将set关键字添加进SQL语句,并且会自动删除多余的逗号
例:
BlogMapper
//set动态更新Blog表
int updateBlogBySet(Map map);
BlogMapper.xml
<!--set动态更新Blog表-->
<update id="updateBlogBySet" parameterType="map">
update mybatis.blog
<set>
<if test="title != null">
title = #{title},
</if>
<if test="author != null">
author = #{author},
</if>
</set>
where id = #{id}
</update>
SQL标签实现SQL语句复用
BlogMapper.xml
<!--SQL标签实现SQL语句复用-->
<sql id="if-views-author">
<if test="views != null">
views = #{views}
</if>
<if test="author != null">
and author = #{author}
</if>
</sql>
<!--if动态查询Blog-->
<select id="queryBlogByIf" parameterType="map" resultType="Blog">
select * from mybatis.blog
<where>
# include标签实现SQL标签嵌入
<include refid="if-views-author"></include>
</where>
</select>
用SQL标签将公共SQL语句提取出来,就可实现复用
Foreach
动态 SQL 的另一个常见使用场景是对集合进行遍历
例:
BlogMapper
//foreach遍历Blog表
List<Blog> queryBlogByForeach(Map map);
BlogMapper.xml
<!--foreach遍历Blog表-->
<!--select * from mybatis.blog where 1=1 and (id=1 or id=2 or id=3)-->
<select id="queryBlogByForeach" parameterType="map" resultType="Blog">
select * from mybatis.blog
<where>
<foreach collection="ids" item="id" open="and (" separator="or" close=")">
id = #{id}
</foreach>
</where>
</select>
BlogTest
Map map = new HashMap();
ArrayList<Integer> ids = new ArrayList<>();
ids.add(1);
ids.add(2);
ids.add(3);
map.put("ids",ids);
List<Blog> blogs = mapper.queryBlogByForeach(map);
for (Blog blog : blogs) {
System.out.println(blog);
}
4.12 缓存(了解即可)
什么是缓存(Cache)
执行查询操作,需要连接数据库,这非常消耗资源,为了优化查询,就需要将查询出的结果暂时存放在可直接读取的内存中,再次查询时,就可直接从内存中获取数据,这就是缓存
(1)缓存:
- 存放在内存中的临时数据
- 将用户经常查询的数据存放在缓存中,就不用从磁盘(关系新数据库数据文件)中查找数据,从而提高效率,避免高并发系统的性能问题
(2)为什么要使用缓存
减少和数据库交互的次数,减少系统资源消耗从而提高效率
(3)什么样的数据能够使用缓存
需要经常查询且不经常修改的数据
Mybatis缓存
Mybattis缓存可以非常方便地定制和配置缓存,能够极大的提高效率
Mybatis系统中默认定义了两级缓存:一级和二级
(1)默认情况下只有一级缓存开启(SqlSession级缓存,也称本地缓存)
(2)二级缓存需要手动开启和配置,是namespace级缓存
(3)为了提高可扩展性,Mybatis定义了缓存接口Cache,可通过实现Cache接口来自定义二级缓存
一级缓存
SqlSession级缓存,作用域是从SqlSession开启到Sqlsession关闭
如下:
SqlSession sqlSession = MybatisUtils.getSqlSession();
..............只有查询数据时才会被缓存.............
sqlSession.close();
- 只有select查询结果会被缓存
- insert、update、delete会刷新缓存
- sqlSession.clearCache();可手动清理缓存
- 缓存刷新没有固定时间间隔
- 各个对象之间的缓存是相互独立的,安全的
二级缓存
namespace级缓存,作用域为整个XXXXMapper.xml
若开启了二级缓存,一级缓存中的数据就会被再次存放进二级缓存中,以便在一级缓存关闭后,更高效的查询先前的数据,不同的Mapper查出的数据会存放在各自的缓存中
- 开启二级缓存
mybatis-config.xml
<!--显式开启二级缓存-->
<setting name="cacheEnabled" value="true"/>
XXXXMapper.xml
<!--在当前Mapper.xml中开启二级缓存-->
<cache/>
也可自定义参数配置
例:缓存策略FIFO,60秒刷新,缓存大小512,内容为只读
<cache
eviction="FIFO"
flushInterval="60000"
size="512"
readOnly="true"/>
二级缓存策略
LRU:最近最少使用:移除最长时间不被使用的对象。
FIFO:先进先出:按对象进入缓存的顺序来移除它们。
SOFT:软引用:基于垃圾回收器状态和软引用规则移除对象。
WEAK:弱引用:更积极地基于垃圾收集器状态和弱引用规则移除对象。
软引用是用来描述一些还有用但并非必须的对象。对于软引用关联着的对象,在系统将要发生内存溢出异常之前,将会把这些对象列进回收范围进行第二次回收。如果这次回收还没有足够的内存,才会抛出内存溢出异常。
弱引用也是用来描述非必须对象的,他的强度比软引用更弱一些,被弱引用关联的对象,在垃圾回收时,如果这个对象只被弱引用关联(没有任何强引用关联他),那么这个对象就会被回收。
【注】
使用默认<cache/>标签时需要先将目标实体类序列化
public class XXXX implements Serializable
缓存总结
- 只要开启了二级缓存,在同一个Mapper下都有效
- 所有的数据都会先存放在一级缓存中,只有当会话提交或关闭后,才会转存到二级缓存中
4.13 Mybatis执行流程
(1)Resources获取加载全局配置文件
(2)实例化SqlSessionFactoryBuilder构造器
(3)XMLConfigBuilder解析配置文件流
(4)返回Configuration对象,包含所有的配置信息
(5)实例化SqlSessionFactory对象
(6)创建transactional事务管理器
(7)创建executor执行器
(8)创建SqlSession
(9)实现CRUD
(10)判断事务是否执行成功,不成功则回滚到第6步
(11)提交事务
(12)关闭
4.14 Mybatis缓存原理
缓存顺序
- 先查看二级缓存
- 再查看一级缓存
- 最后再查找数据库
4.15自定义缓存:ehcache
EhCache 是一个纯Java的进程内缓存框架,具有快速、精干等特点,主要面向通用缓存。Ehcache是一种广泛使用的开源Java分布式缓存。
(现在主流的都在用Redis进行缓存)
使用ehcache
1、导入Maven依赖
<!-- https://mvnrepository.com/artifact/org.mybatis.caches/mybatis-ehcache -->
<dependency>
<groupId>org.mybatis.caches</groupId>
<artifactId>mybatis-ehcache</artifactId>
<version>1.2.1</version>
</dependency>
2、在XXXXMapper.xml中进行配置
<cache type="org.mybatis.caches.ehcache.EhcacheCache"/>
- ehcache配置文件(各种配置请百度)
例:
<?xml version="1.0" encoding="UTF-8" ?>
<ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="http://ehcache.org/ehcache.xsd"
updateCheck="false">
<diskStore path="./tmpdir/Tmp_EhCache"/>
<defaultCache
eternal="false"
maxElementsInMemory="10000"
overflowToDisk="false"
diskPersistent="false"
timeToIdleSeconds="1800"
timeToLiveSeconds="259200"
memoryStoreEvictionPolicy="LRU"/>
<cache
name="cloud_user"
eternal="false"
maxElementsInMemory="5000"
overflowToDisk="false"
diskPersistent="false"
timeToIdleSeconds="1800"
timeToLiveSeconds="1800"
memoryStoreEvictionPolicy="LRU"/>
</ehcache>
- SSM整合项目
5.1 项目环境
开发环境
1、IDEA开发工具
2、MySQL数据库
3、Tomcat服务器
4、Maven开发环境
知识需求
1、MySQL数据库
2、Spring框架
3、JavaWeb开发
4、MyBatis开发
5、前端基础开发
5.2项目开发
项目需求分析
环境搭建
1、数据库
CREATE DATABASE `ssm`;
USE `ssm`;
DROP TABLE IF EXISTS `books`;
CREATE TABLE `bo`books`oks`(
`bookID`INT(10) NOT NULL AUTO_INCREMENT COMMENT '书ID',
`bookname` VARCHAR(100) NOT NULL COMMENT '书名',
`bookCounts` INT(11) NOT NULL COMMENT '数量',
`detail` VARCHAR(200) NOT NULL COMMENT '描述',
PRIMARY KEY (`bookID`)
)ENGINE=INNODB DEFAULT CHARSET=utf8;
USE `books`;
INSERT INTO
`books`(`bookID`,`bookname`,`bookCounts`,`detail`) VALUES
(1,'Java',1,'从入门到放弃'),
(2,'MySQL',10,'从删库到跑路'),
(3,'Linux',5,'从入门到坐牢');
- Maven项目
新建一个Maven项目SSM,添加Web支持
导入Maven依赖
<dependencies>
<!--在<dependencies>标签中配置Maven依赖项-->
<!--Servlet的依赖-->
<!-- https://mvnrepository.com/artifact/javax.servlet/javax.servlet-api -->
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>4.0.1</version>
</dependency>
<!--JSP的依赖-->
<!-- https://mvnrepository.com/artifact/javax.servlet.jsp/javax.servlet.jsp-api -->
<dependency>
<groupId>javax.servlet.jsp</groupId>
<artifactId>javax.servlet.jsp-api</artifactId>
<version>2.3.3</version>
<scope>provided</scope>
</dependency>
<!--JSTL的依赖-->
<!-- https://mvnrepository.com/artifact/javax.servlet.jsp.jstl/jstl-api -->
<dependency>
<groupId>javax.servlet.jsp.jstl</groupId>
<artifactId>jstl-api</artifactId>
<version>1.2</version>
</dependency>
<!--standard标签库的依赖-->
<dependency>
<groupId>taglibs</groupId>
<artifactId>standard</artifactId>
<version>1.1.2</version>
</dependency>
<!--JDBC连接数据库-->
<!-- https://mvnrepository.com/artifact/mysql/mysql-connector-java -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.25</version>
</dependency>
<!--mybatis-->
<!-- https://mvnrepository.com/artifact/org.mybatis/mybatis -->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.5.7</version>
</dependency>
<!--数据库连接池-->
<!-- https://mvnrepository.com/artifact/com.mchange/c3p0 -->
<dependency>
<groupId>com.mchange</groupId>
<artifactId>c3p0</artifactId>
<version>0.9.5.5</version>
</dependency>
<!--Lombok-->
<!-- https://mvnrepository.com/artifact/org.projectlombok/lombok -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.20</version>
</dependency>
<!-- https://mvnrepository.com/artifact/junit/junit -->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.13.2</version>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter</artifactId>
<version>RELEASE</version>
</dependency>
<!--Spring5-->
<!-- https://mvnrepository.com/artifact/org.springframework/spring-webmvc -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>5.3.9</version>
</dependency>
<!--Spring-JDBC-->
<!-- https://mvnrepository.com/artifact/org.springframework/spring-jdbc -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>5.3.9</version>
</dependency>
<!--AOP-->
<!-- https://mvnrepository.com/artifact/org.aspectj/aspectjweaver -->
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.9.8.M1</version>
</dependency>
<!--Mybatis-Spring-->
<!-- https://mvnrepository.com/artifact/org.mybatis/mybatis-spring -->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis-spring</artifactId>
<version>2.0.6</version>
</dependency>
<!--Jackson-->
<!-- https://mvnrepository.com/artifact/com.fasterxml.jackson.core/jackson-databind -->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.13.0-rc1</version>
</dependency>
<!--Fastjson-->
<!-- https://mvnrepository.com/artifact/com.alibaba/fastjson -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.78</version>
</dependency>
</dependencies>
添加resources配置,避免资源导出失败
<!--在<build>中配置resources,来避免我们资源导出失败的问题-->
<build>
<resources>
<resource>
<directory>src/main/resources</directory>
<includes>
<include>**/*.properties</include>
<include>**/*.xml</include>
</includes>
<filtering>false</filtering>
</resource>
<resource>
<directory>src/main/java</directory>
<includes>
<include>**/*.properties</include>
<include>**/*.xml</include>
</includes>
<filtering>false</filtering>
</resource>
</resources>
</build>
构建项目基本结构和配置框架
四个源代码目录
dao、pojo、service、controller
db.properties
driver=com.mysql.jdbc.Driver
url=jdbc:mysql://localhost:3306/mybatis?useSSL=true&useUnicode=true&characterEncoding=UTF-8
username=root
password=964878
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>
<!--引入外部配置文件-->
<properties resource="db.properties"/>
<settings>
<setting name="logImpl" value="STDOUT_LOGGING"/>
<!--显式开启二级缓存-->
<setting name="cacheEnabled" value="true"/>
</settings>
<!--设置自动别名-->
<typeAliases>
<package name="com.OCTDN.pojo."/>
</typeAliases>
<!--环境-->
<environments default="development">
<environment id="development">
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<property name="driver" value="${driver}"/>
<property name="url" value="${url}"/>
<property name="username" value="${username}"/>
<property name="password" value="${password}"/>
</dataSource>
</environment>
</environments>
<mappers>
<mapper resource="com/OCTDN/dao/.xml"/>
</mappers>
</configuration>
applicationContext.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation="http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
https://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/tx
https://www.springframework.org/schema/tx/spring-tx.xsd
http://www.springframework.org/schema/aop
https://www.springframework.org/schema/aop/spring-aop.xsd">
<!--指定需要扫描的软件包,指定包下的注解会自动生效-->
<context:component-scan base-package=""/>
<!--加载注解驱动-->
<context:annotation-config/>
<!--配置视图解析器-->
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<property name="prefix" value="/WEB-INF/jsp"/>
<property name="suffix" value=".jsp"/>
</bean>
</beans>
将IDEA连接至数据库,并将架构选至SSM
配置Tomcat服务器
5.3底层开发
Books
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Books {
private int bookID;
private String bookname;
private int bookCounts;
private String detail;
}
BookMapper接口
public interface BookMapper {
//添加一本书籍
int addBook(Books books);
//删除一本书籍
int deleteBook(@Param("bookid") int id);
//更新一本书籍
int updateBook(Books books);
//根据ID查找一本书籍
Books queryBookByid(@Param("bookid") int id);
//查询所有书籍
List<Books> queryAllBook();
}
BookMapper.xml
<!--namespace(命名空间)=“绑定一个Dao/Mapper接口”-->
<mapper namespace="com.OCTDN.dao.BookMapper">
<insert id="addBook" parameterType="Books">
insert into ssm.books(bookname, bookCounts, detail)
VALUES (#{name},#{counts},#{detail})
</insert>
<delete id="deleteBook">
delete from ssm.books where bookID = #{bookid}
</delete>
<update id="updateBook" parameterType="Books">
update ssm.books
set bookname = #{name},bookCounts = #{counts},detail = #{detail}
where bookID = #{bookid}
</update>
<select id="queryBookByid" resultType="Books">
select * from ssm.books where bookID = #{bookid}
</select>
<select id="queryAllBook" resultType="Books">
select * from ssm.books
</select>
</mapper>
BookService接口
public interface BookService {
//添加一本书籍
int addBook(Books books);
//删除一本书籍
int deleteBook(int id);
//更新一本书籍
int updateBook(Books books);
//根据ID查找一本书籍
Books queryBookByid(int id);
//查询所有书籍
List<Books> queryAllBook();
}
BookServiceImpl
public class BookServiceImpl implements BookService{
//service调用dao,必须设置一个set接口以便Spring管理
//service层的唯一作用就是调用dao层的方法,仅此而已
private BookMapper bookMapper;
public void setBookMapper(BookMapper bookMapper) {
this.bookMapper = bookMapper;
}
@Override
public int addBook(Books books) {
return bookMapper.addBook(books);
}
@Override
public int deleteBook(int id) {
return bookMapper.deleteBook(id);
}
@Override
public int updateBook(Books books) {
return bookMapper.updateBook(books);
}
@Override
public Books queryBookByid(int id) {
return bookMapper.queryBookByid(id);
}
@Override
public List<Books> queryAllBook() {
return bookMapper.queryAllBook();
}
}
5.4 Spring开发
spring-dao.xml
<!--关联数据库配置文件db.properties-->
<context:property-placeholder location="classpath:db.properties"/>
<!--配置数据库连接池DataSource
dbcp:半自动化,需要手动连接
C3P0:全自动化连接,自动化加载配置文件,并且可以自动设置到对象中
-->
<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
<!--注意,四个属性必须以jdbc.开头包括配置文件中-->
<property name="driverClass" value="${jdbc.driver}"/>
<property name="jdbcUrl" value="${jdbc.url}"/>
<property name="user" value="${jdbc.username}"/>
<property name="password" value="${jdbc.password}"/>
<!-- c3p0连接池的私有属性 -->
<property name="maxPoolSize" value="30"/>
<property name="initialPoolSize" value="10"/>
<property name="minPoolSize" value="10"/>
<!-- 关闭连接后不自动commit -->
<property name="autoCommitOnClose" value="false"/>
<!-- 获取连接超时时间 -->
<property name="checkoutTimeout" value="10000"/>
<!-- 当获取连接失败重试次数 -->
<property name="acquireRetryAttempts" value="2"/>
</bean>
<!--配置SqlSessionFactory-->
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<property name="dataSource" ref="dataSource"/>
<!--绑定Mybatis配置文件-->
<property name="configLocation" value="classpath:mybatis-config.xml"/>
</bean>
<!--配置Dao接口自动扫描,即动态的实现Dao接口并注入到IOC容器中-->
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<!--注入SqlSessionFactory-->
<property name="sqlSessionFactoryBeanName" value="sqlSessionFactory"/>
<!--设置需要扫描的包-->
<property name="basePackage" value="com.OCTDN.dao"/>
</bean>
spring-service.xml
<!--设置需要扫描的包-->
<context:component-scan base-package="com.OCTDN.service"/>
<!--将业务类注入到IOC容器中-->
<bean id="BookServiceImpl" class="com.OCTDN.service.BookServiceImpl">
<!--引用BookMapper接口
由于spring-dao中配置了自动扫描,
所以dao中的接口会自动注入到IOC容器中,名称默认为首字母小写
-->
<property name="bookMapper" ref="bookMapper"/>
</bean>
<!--配置事务管理器-->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<!--注入数据库连接池-->
<property name="dataSource" ref="dataSource"/>
</bean>
5.5 SpringMVC开发
web.xml
<!--配置SpringMVC-->
<servlet>
<servlet-name>springmvc</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:applicationContext.xml</param-value>
</init-param>
</servlet>
<servlet-mapping>
<servlet-name>springmvc</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
<!--配置中文乱码过滤器-->
<filter>
<filter-name>encodingFilter</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>
</filter>
<filter-mapping>
<filter-name>encodingFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
spring-mvc.xml
<!--指定需要扫描的软件包,指定包下的注解会自动生效-->
<context:component-scan base-package="com.OCTDN.controller"/>
<!--加载注解驱动-->
<context:annotation-config/>
<mvc:default-servlet-handler/>
<mvc:annotation-driven/>
<!--配置视图解析器-->
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<property name="prefix" value="/WEB-INF/jsp/"/>
<property name="suffix" value=".jsp"/>
</bean>
applicationContext.xml
<import resource="spring-dao.xml"/>
<import resource="spring-service.xml"/>
<import resource="spring-mvc.xml"/>
5.6 Controller和视图层开发
BookController
@Controller
@RequestMapping("/book")
public class BookController {
//使用注解实现自动装配接口Bean,并使用指定实现类
//@Autowired:使目标接口自动装配Bean,无需set方法
@Autowired
//@Qualifier:告诉@Autowired具体使用那个实现类
@Qualifier("BookServiceImpl")
private BookService bookService;
@RequestMapping("/allBook")
public String allBook(Model model){
List<Books> books = bookService.queryAllBook();
model.addAttribute("allBook", books);
return "allBook";
}
//用Controller来控制页面跳转
@RequestMapping("/toAddBook")
public String toAddBook(){
return "addBook";
}
@RequestMapping("/addBook")
public String addBook(Books books,Model model){
bookService.addBook(books);
//重定向回主页,转发会出现重读提交的情况,同时重新请求才能显示书籍信息
return "redirect:/book/allBook";
}
//RestFul风格接收前端传回的参数
@RequestMapping("/toUpDateBook/{bookID}")
//给id参数起别名,用于接收参数
public String toUpDateBook(@PathVariable("bookID") int id,Model model){
Books books = bookService.queryBookByid(id);
model.addAttribute("book",books);
return "updateBook";
}
@RequestMapping("/updateBook")
public String updateBook(Books book,Model model){
bookService.updateBook(book);
return "redirect:/book/allBook";
}
//RestFul风格接收前端传回的参数
@RequestMapping("/deleteBook/{bookID}")
//给id参数起别名,用于接收参数
public String deleteBook(@PathVariable("bookID") int id){
bookService.deleteBook(id);
return "redirect:/book/allBook";
}
}
index.jsp
<html>
<head>
<title>SSM</title>
<style>
a{
text-decoration: none;
color: black;
font-size: 45px;
}
h1 {
width: 400px;
height: 100px;
/*外边距自动居中*/
margin: 100px auto;
/*文本居中*/
text-align: center;
/*行高,上面居中*/
line-height: 100px;
background: aqua;
/*圆角边框*/
border-radius: 20px;
}
</style>
</head>
<body>
<h1>
<a href="${pageContext.request.contextPath}/book/allBook">全部书籍</a>
</h1>
</body>
</html>
allBook.jsp
<html>
<head>
<title>书籍展示</title>
<%--BootStrap美化界面--%>
<link href="https://cdn.staticfile.org/twitter-bootstrap/3.3.7/css/bootstrap.min.css" rel="stylesheet">
</head>
<body>
<!--BootStrap页面容器边框-->
<div class="container">
<!--清除div块级元素的行浮动-->
<div class="row clearfix">
<!--将中等屏幕分为12列-->
<div class="col-md-12 column">
<!--设置为页面头部-->
<div class="page-header">
<h1>
<!--BootStrap小标签-->
<small>书籍列表</small>
</h1>
</div>
</div>
</div>
<div class="row">
<div class="col-md-4 column">
查询书籍:<input type="text" name="bookID" placeholder="编号">
<input type="text" name="bookname" placeholder="名称">
<input type="text" name="bookCounts" placeholder="数量">
<input type="text" name="detail" placeholder="描述">
</div>
</div>
<div class="row">
<div class="col-md-4 column">
<!--btn-btn-primary:两种样式结合,btn:按钮,btn-primary:按钮为基本样式按钮,颜色为蓝色-->
<a class="btn-btn-primary" href="${pageContext.request.contextPath}/book/toAddBook">新增</a>
</div>
</div>
<div class="row clearfix">
<div class="col-md-12 column">
<!--BootStrap的table样式,hover:鼠标触碰行自动变色,striped:表格-->
<table class="table table-hover table-striped">
<!--表格头-->
<theader>
<tr>
<th>书籍编号</th>
<th>书籍名称</th>
<th>书籍数量</th>
<th>书籍描述</th>
<th>操作</th>
</tr>
<!--表格主体-->
<tbody>
<%--将allBook遍历到页面中,var:遍历出的变量名,items:被遍历的集合--%>
<c:forEach var="book" items="${allBook}">
<tr>
<td>${book.bookID}</td>
<td>${book.bookname}</td>
<td>${book.bookCounts}</td>
<td>${book.detail}</td>
<td>
<!--RestFul风格传输目标ID给Controller,以便更新页面显示原有数据-->
<a href="${pageContext.request.contextPath}/book/toUpDateBook/${book.getBookID()}">修改</a>
<a href="${pageContext.request.contextPath}/book/deleteBook/${book.getBookID()}">删除</a>
</td>
</tr>
</c:forEach>
</tbody>
</theader>
</table>
</div>
</div>
</div>
</body>
</html>
addBook.jsp
<html>
<head>
<title>新增书籍</title>
<meta name="viewport" content="width=dvice-width,initial-scale=1.0">
<!--引入Bootstrap-->
<link href="https://cdn.staticfile.org/twitter-bootstrap/3.3.7/css/bootstrap.min.css" rel="stylesheet">
</head>
<body>
<div class="container">
<div class="roe clearfix">
<div col-md-12 column>
<div class="page-header">
<h1>
<small>新增书籍</small>
</h1>
</div>
</div>
</div>
<form action="${pageContext.request.contextPath}/book/addBook" method="post">
书籍名称:<input type="text" name="bookname" required><br><br><br>
书籍数量:<input type="text" name="bookCounts" required><br><br><br>
书籍详情:<input type="text" name="detail" required><br><br><br>
<input type="submit" value="提交">
</form>
</div>
</body>
</html>
updateBook.jsp
<html>
<head>
<title>Title</title>
<link href="https://cdn.staticfile.org/twitter-bootstrap/3.3.7/css/bootstrap.min.css" rel="stylesheet">
</head>
<body>
<div class="container">
<div class="row clearfix">
<div class="row col-md-12 column">
<div class="page-header">
<h1>
<small>修改信息</small>
</h1>
</div>
</div>
</div>
<form action="${pageContext.request.contextPath}/book/updateBook" method="post">
<input type="hidden" name="bookID" value="${book.getBookID()}">
书籍名称:<input type="text" name="bookname" value="${book.getBookname()}" required>
书籍数量:<input type="text" name="bookCounts" value="${book.getBookCounts()}" required>
书籍描述:<input type="text" name="detail" value="${book.getDetail()}" required>
<input type="submit" value="提交">
</form>
</div>
</body>
</html>