SSM框架
一、Spring
1、Spring框架概述:
1.spring是一个轻量级的开源javaEE框架,简化开发
2.spring可以解决企业应用开发的复杂性
3.spring的两个核心部分:IOC和AOP
(1)IOC:控制反转,将创建对象过程交给spring进行管理
(2)Aop:面向切面,不修改源代码的情况下,进行功能的增强
4.特点:
- 方便解耦,简化开发:
- AOP编程支持
- 方便测试
- 方便集成其他框架
- 降低JavaEE API的使用难度
- 方便事务操作
- java源码是经典学习范例
下载spring所需的jar包
5.入门案例
使用spring创建对象(使用配置文件或者注解)
(1).创建普通类,在这个类创建普通方法
public class User {public void add(){System.out.println("!嘿嘿");}
}
(2).创建配置文件,在配置文件配置创建的对象
<!--配置User对象的创建--><bean id="user" class="com.fjp.spring5.User"></bean>
(3)测试
@Test
public void testAdd(){//1.加载配置文件ApplicationContext context=new ClassPathXmlApplicationContext("bean1.xml");//2.获取配置创建的对象User user = context.getBean("user", User.class);System.out.println(user);user.add();
}
2、IOC容器
原理:
控制反转,将对象的创建和调用的过程,都交给Spring进行管理
目的:降低耦合度
ioc:反转控制
1.IOC思想
1.获取资源的传统方式
2.反转控制方式获取资源
3.DI 依赖注入
DI是IOC的另一种表达方式,即组件以一些预先定义好的方式接收来自于容器的资源注入,相对于IOC而言,这种表述更加直接。
2.IOC在Spring中的实现
Spring的IOC容器就是IOC思想的一个落地的产品实现,IOC容器中管理的组件也叫bean。在创建bean之前,首先需要创建IOC容器,Spring提供了IOC容器的两种实现方式
-
BeanFactory
这是IOC容器的基本实现,是Spring内部使用的接口,面向Spring本身,不提供给开发人员使用
加载配置文件时不会创建对象,在获取对象时,才创建对象
-
ApplicationContext
BeanFactory的子接口,提供了很多高级特性,面向Spring的使用者,几乎所有场合都使用AppliationContext,而不是底层的BeanFactory
加载配置文件时,就会创建配置文件内的对象
-
ApplicationContext的主要实现类
依赖
<dependencies>
<!-- 基于Maven依赖传递性,导入spring-context依赖即可导入当前所需所有jar包 --><dependency><groupId>org.springframework</groupId><artifactId>spring-context</artifactId><version>5.3.1</version></dependency><!-- junit测试 --><dependency><groupId>junit</groupId><artifactId>junit</artifactId><version>4.12</version><scope>test</scope></dependency>
</dependencies>
3.Bean管理
3.1什么是Bean管理
Spring 创建对象
Spring 注入属性
3.2Bean管理操作方式
基于xml配置文件方式实现
基于注解方式实现
4.基于xml管理Bean
DI:依赖注入,就是注入属性
第一种:使用set方法进行注入
创建set方法
public class User {private String name;public void setName(String name) {this.name = name;} }
第二种:使用有参构造进行注入
使用有参构造
- 创建Maven Module
- 引入依赖
3.创建HelloWorld类
public class HelloWorld {public void sayHello(){System.out.println("hello,spring");}
}
4.创建applicationContext.xml文件
<!--bean:配置一个bean对象,将对象交给IOC容器管理属性:id:bean的唯一标识,不能重复class:设置bean对象所对应的类型name:作用和id一样,不能加入特殊符号
创建对象时,默认执行无参构造完成对象创建
--><bean id="helloworld" class="com.fjp.spring.pojo.HelloWorld"></bean>
5.测试
@Test
public void test(){//获取IOC容器ApplicationContext ioc= new ClassPathXmlApplicationContext("applicationContext.xml");//获取IOC中的bean对象HelloWorld helloworld = (HelloWorld) ioc.getBean("helloworld");helloworld.sayHello();
}
4.IOC创建对象的方式
可以在实体类在不创建有参构造器,必须创造无参构造器
public class Student {private Integer sid;private String sname;private Integer age;private String gender;public Student() {}
}
<bean id="studentOne" class="com.fjp.spring.pojo.Student"></bean>
@Test
public void testIOC(){ApplicationContext ioc=new ClassPathXmlApplicationContext("applicationContext.xml");Student studentOne =(Student)ioc.getBean("studentOne");System.out.println(studentOne);}
1.获取bean的三种方式
1.根据bean的id获取,获取得到的返回类型为Object,需要强制转换类型
Student studentOne =(Student)ioc.getBean("studentOne");
2.根据bean的类型获取
Student bean = ioc.getBean(Student.class);
3.根据bean的类型和id获取
Student bean = ioc.getBean("studentOne",Student.class);
注意:
如果组件类实现了接口,根据接口类可以获取bean,前提是bean是唯一的
如果一个接口有多个实现类,这些实现类都配置了bean,根据接口类不能获取bean,因为bean不唯一
2.依赖注入值setter注入
实现ioc的具体方式。
private String name;public void setName(String name) {this.name = name;
}
<!-- property:通过成员变量的set方法进行赋值name:设置需要赋值的属性名(和set方法有关)value:设置为属性所赋的值
-->
<bean id="studentTwo" class="com.fjp.spring.pojo.Student"><property name="sid" value="1001"></property><property name="sname" value="李大头"></property><property name="age" value="18"></property><property name="gender" value="n"></property>
</bean>
@Test
public void testDi(){ApplicationContext ioc=new ClassPathXmlApplicationContext("applicationContext.xml");Student studentTwo = ioc.getBean("studentTwo", Student.class);System.out.println(studentTwo);
}
3.实现构造器注入
public class Student {private int id;private String name;private int age;public Student(int id, String name, int age) {this.id = id;this.name = name;this.age = age;}
}
<bean id="studentThree" class="com.fjp.spring.pojo.Student"><constructor-arg name="id" value="1"></constructor-arg><constructor-arg name="name" value="李大脑袋"></constructor-arg><constructor-arg name="age" value="1"></constructor-arg><constructor-arg name="gender" value="男"></constructor-arg>
</bean>
@Test
public void testConstruct(){ApplicationContext ioc=new ClassPathXmlApplicationContext("applicationContext.xml");Student studentTwo = ioc.getBean("studentThree", Student.class);System.out.println(studentTwo);
}
4.p名称空间注入
可以简化基于xml注入
第一步,添加p名称空间在配置文件中
xmlns="http://www.springframework.org/schema/beans"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xmlns:p="http://www.springframework.org/schema/p"xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
第二步 set方法注入属性
<bean id="student" class="com.fjp.pojo.Student" p:id="1" p:name="张三" p:age="15"></bean>
4.特殊值处理
-
字面量赋值:
什么是字面量
int a=10;
声明一个变量a,初始化为10,此时不代表字母a,而是作为一个变量的名字,当我们引用a的时候,实际上拿到的值是10
如果a是带单引号的’a ',那么它现在不是一个变量,他就是代表a这个字母本身,这就是字面量,所以字面量没有引申含义,就是看到的数据本身
-
null值
为属性赋值为null <property name="sname"><null></null> </property>
-
xml实体
<!--大于号和小于号在xml文档中用来定义标签的开始,不能随意使用,使用时必须使用xml实体来代替<>
-->
<constructor-arg name="sname" value="<李大脑袋>"></constructor-arg>
4.CDATA节(<![CDATA[<自己的值>]]>)
<constructor-arg name="sname"><value><![CDATA[<王五>]]></value></constructor-arg>
内容原样解析,不能写在属性中,以标签的方式写
快捷键CD
5.为类类型的属性赋值
1.ref(外部bean)
public class Clazz {private Integer cid;private String cname;
public class Student {private Integer sid;private String sname;private Integer age;private String gender;private Clazz clazz;
<bean id="studentFive" class="com.fjp.spring.pojo.Student"><property name="sid" value="1111"></property><property name="sname" value="李四"></property><property name="age" value="12"></property><property name="gender" value="男"></property><!--ref:引用ioc容器中的某个bean的id--><property name="clazz" ref="clazzOne"></property>
</bean><bean id="clazzOne" class="com.fjp.spring.pojo.Clazz"><property name="cid" value="123"></property><property name="cname" value="嗯嗯"></property>
</bean>
@Test
public void testClazz() {ApplicationContext ioc = new ClassPathXmlApplicationContext("applicationContext.xml");Student student = ioc.getBean("studentFive", Student.class);System.out.println(student);
}
2.级联
<!--级联赋值方式一--><bean id="student" class="com.fjp.pojo.Student"><property name="id" value="1"></property><property name="name" value="lucy"></property><property name="age" value="18"></property><property name="collage" ref="collage"></property></bean><bean id="collage" class="com.fjp.pojo.Collage"><property name="cname" value="机械"></property></bean>
<!--级联赋值方式二--><bean id="student" class="com.fjp.pojo.Student"><property name="id" value="1"></property><property name="name" value="lucy"></property><property name="age" value="18"></property><property name="collage" ref="collage"></property><property name="collage.cname" value="collage"></property></bean>
3.内部bean
public class Student {private int id;private String name;private int age;private Collage collage;public void setCollage(Collage collage) {this.collage = collage;}
public class Collage {private String cname;public void setCname(String cname) {this.cname = cname;}
}
<!-- 内部bean--><bean id="student" class="com.fjp.pojo.Student"><property name="id" value="1"></property><property name="name" value="lucy"></property><property name="age" value="18"></property>
<!-- 设置对象属性--><property name="collage"><bean id="dept" class="com.fjp.pojo.Collage"><property name="cname" value="信息院"></property></bean></property></bean>
内部bean通过ioc容器直接获取,只能在当前bean的内部使用
6…为数组类型的属性赋值
<property name="hobby"><array><value>抽烟</value><value>抽烟</value><value>抽烟</value></array>
</property>
7.为list集合类型的属性赋值
<property name="students"><list><ref bean="studentFive"></ref><ref bean="studentThree"></ref><ref bean="studentTwo"></ref></list>
</property>
8.为map集合类型的属性赋值
public class Student {private Integer sid;private String sname;private Integer age;private String gender;private Clazz clazz;private String[] hobby;private Map<String,Teacher> teachers;
public class Teacher {private int tid;private String tname;
<bean id="teacherOne" class="com.fjp.spring.pojo.Teacher"><property name="tid" value="01"></property><property name="tname" value="英语"></property>
</bean>
<bean id="teacherTwo" class="com.fjp.spring.pojo.Teacher"><property name="tid" value="02"></property><property name="tname" value="数学"></property>
</bean>
<property name="teachers"><map><entry key="01" value-ref="teacherOne"></entry><entry key="02" value-ref="teacherTwo"></entry></map>
</property>
方式2:
<property name="teachers" ref="teacherMap"></property>
<util:map id="teacherMap"><entry key="01" value-ref="teacherOne"></entry><entry key="02" value-ref="teacherTwo"></entry>
</util:map>
9.p命名空间
<bean id="studentSix" class="com.fjp.spring.pojo.Student" p:sid="1006" p:sname="555"p:teachers-ref="teacherMap"></bean>
10.引入外部属性文件
1.加入依赖:
<!-- MySQL驱动 --><dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId><version>5.1.49</version></dependency><!-- 数据源 --><dependency><groupId>com.alibaba</groupId><artifactId>druid</artifactId><version>1.0.31</version></dependency>
2.bean引入数据库
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource"><property name="driverClassName" value="com.mysql.jdbc.Driver"></property><property name="url" value="jdbc:mysql://localhost:3306/ssm"></property><property name="username" value="root"></property><property name="password" value="19480204"></property>
</bean>
3.方式二
<!--引入jdbc.properties 之后通过${key}的方式访问value--><context:property-placeholder location="jdbc.properties"></context:property-placeholder><bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource"><property name="driverClassName" value="${jdbc.driver}"></property>
11.bean的作用域
<!--scope设置bean的作用域,
singleton:代表单例,使用多(默认),加载Spring配置文件的时候,会创建单实例对象
prototype:多例,在调用getBean方法时,创建对象
--><bean id="student" class="com.fjp.spring.pojo.Student" scope="prototype"><property name="sid" value="1"></property><property name="sname" value="李磊"></property></bean>
@Test
public void testScope(){ApplicationContext ioc =new ClassPathXmlApplicationContext("spring-scope.xml");Student stu1 = ioc.getBean(Student.class);Student stu2 = ioc.getBean(Student.class);System.out.println(stu1.equals(stu2));//结果为false
}
12.bean的生命周期
1、生命周期
(1)从对象创建到对象销毁的过程
2、bean生命周期
(1)通过构造器创建 bean 实例(无参数构造)
(2)为 bean 的属性设置值和对其他 bean 引用(调用 set 方法)
(3)调用 bean 的初始化的方法(需要进行配置初始化的方法)
(4)bean 可以使用了(对象获取到了)
(5)当容器关闭时候,调用 bean 的销毁的方法(需要进行配置销毁的方法)
public class User {private Integer id;private String username;private String password;private Integer age;public User() {System.out.println("1.实例化");}public User(Integer id, String username, String password, Integer age) {this.id = id;this.username = username;this.password = password;this.age = age;}public Integer getId() {return id;}public void setId(Integer id) {System.out.println("2.依赖注入");this.id = id;}public String getUsername() {return username;}public void setUsername(String username) {this.username = username;}public String getPassword() {return password;}public void setPassword(String password) {this.password = password;}public Integer getAge() {return age;}public void setAge(Integer age) {this.age = age;}@Overridepublic String toString() {return "User{" +"id=" + id +", username='" + username + '\'' +", password='" + password + '\'' +", age=" + age +'}';}public void initMethod(){System.out.println("3.初始化");}public void destroyMethod(){System.out.println("4.销毁");}
}
<bean id="user" class="com.fjp.spring.pojo.User" init-method="initMethod" destroy-method="destroyMethod"><property name="id" value="1"></property><property name="username" value="admin"></property><property name="password" value="123456"></property><property name="age" value="20"></property>
</bean>
<bean id="myBeanPostProcessor" class="com.fjp.spring.process.MyBeanPostProcessor"></bean>
public class MyBeanPostProcessor implements BeanPostProcessor {@Overridepublic Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {//在初始化之前执行System.out.println("后置处理器的 postProcessBeforeInitialization");return bean;}@Overridepublic Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {//初始化之后执行System.out.println("后置处理器的 postProcessAfterInitialization");return bean;}
}
@Test
public void tesT(){//ConfigurableApplicationContext是ApplicationContext的子接口,其中扩展了刷新和关闭的方法/*生命周期的阶段1.实例化2.依赖注入3.初始化,需要bean的init-method方法指定初始化方法4.ioc容器关闭时销毁,需要bean的destroy-method的方法指定销毁方法注意:如果bean的作用域为单例时,生命周期的前三个步骤会在获取IOC容器时执行如果bean的作用域为多例时,生命周期的前三个步骤会在获取bean时执行*/ConfigurableApplicationContext ioc = new ClassPathXmlApplicationContext("spring-lifecycle.xml");User user = ioc.getBean(User.class);System.out.println(user);ioc.close();}
bean的后置处理器会在生命周期的初始化前后添加额外的操作,需要实现BeanPostProcessor接口, 且配置到IOC容器中,
bean后置处理器不是单独针对某一个bean生效,而是针对IOC容 器中所有bean都会执行
13.FactoryBean
FactoryBean是Spring提供的一种整合第三方框架的常用机制。和普通的bean不同,配置一个 FactoryBean类型的bean,在获取bean的时候得到的并不是class属性中配置的这个类的对象,而是 getObject()方法的返回值。通过这种机制,Spring可以帮我们把复杂组件创建的详细过程和繁琐细节都 屏蔽起来,只把最简洁的使用界面展示给我们。 将来我们整合Mybatis时,Spring就是通过FactoryBean机制来帮我们创建SqlSessionFactory对象的。
* @Description: FactoryBean是一个接口,需要创建一个类实现该接口,* 有三个方法* 1.getObject 通过一个对象交给IOC容器管理* 2.getObjectType设置所提供的对象的类型* 3.isSingleton 所提供的对象是否单例* 当我们把FactoryBean的实现类配置为bean时,会将当前类中getObject所返回的对象交个IOC容器来管理*/public class UserFactoryBean implements FactoryBean<User> {@Overridepublic User getObject() throws Exception {return new User();}@Overridepublic Class<?> getObjectType() {return User.class;}
}
<bean class="com.fjp.spring.factory.UserFactoryBean"></bean>
@Test
public void test(){ApplicationContext ioc=new ClassPathXmlApplicationContext("spring-factory.xml");final User user = ioc.getBean(User.class);System.out.println(user);
}
14.基于xml的自动装配
自动装配:
根据指定的策略,在IOC容器中匹配某一个bean,自动为指定的bean中所依赖的类类型或接口类 型属性赋值
手动装配
public class UserController {private UserService userService;public UserService getUserService() {return userService;}public void setUserService(UserService userService) {this.userService = userService;}public void saveUser(){userService.saveUser();}
}
public interface UserService {//保存用户信息void saveUser();
}
public class UserServiceImpl implements UserService {private UserDao userDao;@Overridepublic void saveUser() {userDao.saveUser();}public UserDao getUserDao() {return userDao;}public void setUserDao(UserDao userDao) {this.userDao = userDao;}
}
public interface UserDao {//保存用户信息void saveUser();
}
public class UserDaoImpl implements UserDao {@Overridepublic void saveUser() {System.out.println("保存成功");}
}
<bean class="com.fjp.spring.controller.UserController"><property name="userService" ref="userService"></property>
</bean><bean id="userService" class="com.fjp.spring.service.impl.UserServiceImpl"><property name="userDao" ref="userDao"></property>
</bean><bean id="userDao" class="com.fjp.spring.dao.impl.UserDaoImpl"></bean>
@Test
public void testAutowire(){ApplicationContext ioc=new ClassPathXmlApplicationContext("spring-autowire.xml");UserController userController= ioc.getBean(UserController.class);userController.saveUser();}
自动装配
1.bytype
<bean class="com.fjp.spring.controller.UserController" autowire="byType">
<!--<property name="userService" ref="userService"></property>--></bean><bean id="userService" class="com.fjp.spring.service.impl.UserServiceImpl" autowire="byType">
<!--<property name="userDao" ref="userDao"></property>--></bean><bean id="userDao" class="com.fjp.spring.dao.impl.UserDaoImpl"></bean>
/*** 自动装配,根据指定的策略,在ioc容器中匹配某个bean,自动为bean中的类类型属性或接口类型的属性进行赋值* autowire自动装配的策略:* no、default:表示不装配,即bean中的属性不会自动匹配某个bean为属性赋值,此时属性使用默认值*byType:根据要赋值属性的类型,在ioc容器中匹配某个bean,为属性赋值* 注意:1.若通过类型,没有找到类型匹配的bean,此时不装配,使用默认值* 2.若通过类型找到了多个类型匹配的bean,此时抛出异常 nouniquebean的异常* 总结:使用bytype自动装配时,ioc容器中有且只有一个类型匹配的bean能够为属性赋值*/
2.byname
byName:将要赋值的属性的属性名作为bean的id在IOC容器中匹配某个bean为属性赋值
* 总结:当类型匹配的bean有多个时,此时可以使用byname进行自动装配
<bean class="com.fjp.spring.controller.UserController" autowire="byName">
<!--<property name="userService" ref="userService"></property>--></bean><bean id="userService" class="com.fjp.spring.service.impl.UserServiceImpl" autowire="byName">
<!--<property name="userDao" ref="userDao"></property>--></bean><bean id="Service" class="com.fjp.spring.service.impl.UserServiceImpl" autowire="byName">
<!--<property name="userDao" ref="userDao"></property>--></bean><bean id="userDao" class="com.fjp.spring.dao.impl.UserDaoImpl"></bean><bean id="Dao" class="com.fjp.spring.dao.impl.UserDaoImpl"></bean>
4.基于注解管理bean
1.标记和扫描
①注解
和 XML 配置文件一样,注解本身并不能执行,注解本身仅仅只是做一个标记,具体的功能是框架检测 到注解标记的位置,然后针对这个位置按照注解标记的功能来执行具体操作。
本质上:所有一切的操作都是Java代码来完成的,XML和注解只是告诉框架中的Java代码如何执行。
②扫描
Spring 为了知道程序员在哪些地方标记了什么注解,就需要通过扫描的方式,来进行检测。然后根据注 解进行后续操作。
2.标识组件的常用注解
@Component:将类标识为普通组件
@Controller:将类标识为控制层组件
@Service:将类标识为业务层组件
@Repository:将类标识为持久层组
@Controller
public class UserController {
}
@Repository
public class UserDaoImpl implements UserDao {
}
@Service
public class UserServiceImpl implements UserService {
}
<!--扫描组件--><context:component-scan base-package="com.fjp.spring"></context:component-scan>
@Test
public void test(){ApplicationContext ioc= new ClassPathXmlApplicationContext("spring_ioc_annotation.xml");UserController controller = ioc.getBean(UserController.class);System.out.println(controller);UserService service = ioc.getBean(UserService.class);System.out.println(service);UserDao userDao = ioc.getBean(UserDao.class);System.out.println(userDao);
}
扫描组件
<!--扫描组件排除扫描<context:exclude-filter type="annotation" expression="org.springframework.stereotype.Controller"/>type="annotation"排除扫描指定注解 expression:排除注解的全类名type="assignable"排除扫描指定类包含扫描<context:include-filter type="annotation" expression="org.springframework.stereotype.Controller"/>注意:需要在context:component-scan标签中设置use-default-filters="false"默认为true use-default-filters属性:取值false表示关闭默认扫描规则use-default-filters="false",因为默认规则即扫描指定包下所有类--><context:component-scan base-package="com.fjp.spring" use-default-filters="false">
<!-- <context:exclude-filter type="annotation" expression="org.springframework.stereotype.Controller"/>--><context:include-filter type="annotation" expression="org.springframework.stereotype.Controller"/></context:component-scan></beans>
3.基于注解管理bean之bean的id
通过注解+扫描所配置的bean的id,默认值为类的小驼峰,即类名的首字母小写
可以通过注解后设置特定的id,@Controller(“controller”)
@Test
public void test(){ApplicationContext ioc= new ClassPathXmlApplicationContext("spring_ioc_annotation.xml");UserController controller = ioc.getBean("userController",UserController.class);System.out.println(controller);UserService service = ioc.getBean("userServiceImpl",UserService.class);System.out.println(service);UserDao userDao = ioc.getBean("userDaoImpl",UserDao.class);System.out.println(userDao);
}
4.基于注解的自动装配
(1)autoWried :根据属性类型进行自动装配
第一步:在service和dao对象创建,在service和dao类添加创建对象注解
第二步:在service中,定义dao类型的属性,在属性上添加注解,不需要添加set方法
(2)Qualifiter:根据属性名进行注入
和autowried一起使用
(3)Resource:可以根据类型注入,可以根据名称注入
@Resource(name = “dao实现类”)–通过名成注入
@Resource类型注入
(4)value :注入普通类型属性
@Value(value = "abc") private String name;
@autowired
1.在成员变量上直接标记@Autowired注解即可完成自动装配,不需要提供setXxx()方法。以后我们在项
目中的正式用法就是这样。
@Autowired注解可以标记在构造器和set方法上
工作流程
5.纯注解开发
(1)创建配置类,替代xml配置文件
3.AOP
3.1场景模拟
3.1.1声明接口
public interface Calculator {/*** 加减乘除* @param i* @param j* @return*/int add(int i,int j);int sub(int i,int j);int mul(int i,int j);int div(int i,int j);
}
public class CalculatorImpl implements Calculator {@Overridepublic int add(int i, int j) {System.out.println("日志,方法:add,参数:"+i+","+j);int result=i+j;System.out.println("方法内部的 result"+ result);System.out.println("日志,方法:add,结果"+result);return result;}@Overridepublic int sub(int i, int j) {System.out.println("日志,方法:sub,参数:"+i+","+j);int result=i-j;System.out.println("方法内部的 result"+ result);System.out.println("日志,方法:sub,结果"+result);return result;}@Overridepublic int mul(int i, int j) {System.out.println("日志,方法:mul,参数:"+i+","+j);int result=i*j;System.out.println("方法内部的 result"+ result);System.out.println("日志,方法:mul,结果"+result);return result;}@Overridepublic int div(int i, int j) {System.out.println("日志,方法:div,参数:"+i+","+j);int result=i/j;System.out.println("方法内部的 result"+ result);System.out.println("日志,方法:div,结果"+result);return result;}
}
1.现有的代码缺陷
对核心功能的加减乘除有干扰,导致程序员在开发核心业务功能时分散精力
附加功能分散在各个业务功能方法中,不利于维护同一
2.解决思路
解决这两个问题,核心就是:解耦,我们需要把附加功能从业务代码中抽取出来
3.困难
解决问题的困难,要抽取的代码在方法内部,靠以前把子类中的重复代码抽取到父类的方式没发解决,所以需要引入新的技术
3.2代理模式
二十三种设计模式中的一种,属于结构型模式。它的作用就是通过提供一个代理类,让我们在调用目标
方法的时候,不再是直接对目标方法进行调用,而是通过代理类间接调用。让不属于目标方法核心逻辑
的代码从目标方法中剥离出来——解耦。调用目标方法时先调用代理对象的方法,减少对目标方法的调
用和打扰,同时让附加功能能够集中在一起也有利于统一维护
代理:将非核心逻辑剥离出来以后,封装这些非核心逻辑的类、对象、方法
目标:被代理套用了非核心逻辑代码的类,对象,方法
目标类实现什么功能,代理类也要实现相应的功能
1.静态代理
package com.fjp.spring.proxy.impl;import com.fjp.spring.proxy.Calculator;/*** @version jdk1.8* @Date: 2022/12/31/19:51* @Description: 致敬*/public class CalculatorImpl implements Calculator {@Overridepublic int add(int i, int j) {int result=i+j;System.out.println("方法内部的 result"+ result);return result;}@Overridepublic int sub(int i, int j) {int result=i-j;System.out.println("方法内部的 result"+ result);return result;}@Overridepublic int mul(int i, int j) {int result=i*j;System.out.println("方法内部的 result"+ result);return result;}@Overridepublic int div(int i, int j) {int result=i/j;System.out.println("方法内部的 result"+ result);return result;}
}
package com.fjp.spring.proxy.impl;import com.fjp.spring.proxy.Calculator;/*** @version jdk1.8* @Date: 2023/01/01/17:16* @Description: 致敬*/public class CalculatorStaticProxy implements Calculator
{private CalculatorImpl calculatorimpl;public CalculatorStaticProxy(CalculatorImpl calculatorimpl){this.calculatorimpl = calculatorimpl;}@Overridepublic int add(int i, int j){System.out.println("日志,方法:add,参数:"+i+","+j);int result= calculatorimpl.add(i, j);System.out.print("日志,方法:add,结果"+result);return result;}@Overridepublic int sub(int i, int j){System.out.println("日志,方法:sub,参数:"+i+","+j);int result=calculatorimpl.sub(i,j);System.out.print("日志,方法:sub,结果"+result);return result;}@Overridepublic int mul(int i, int j){System.out.println("日志,方法:mul,参数:"+i+","+j);int result=calculatorimpl.mul(i,j);System.out.print("日志,方法:mul,结果"+result);return result;}@Overridepublic int div(int i, int j) {System.out.println("日志,方法:div,参数:"+i+","+j);int result=calculatorimpl.div(i,j);System.out.print("日志,方法:div,结果"+result);return result;}
}
package com.fjp.proxy;import com.fjp.spring.proxy.impl.CalculatorImpl;
import com.fjp.spring.proxy.impl.CalculatorStaticProxy;
import org.junit.Test;
import org.omg.CORBA.PUBLIC_MEMBER;/*** * @Date: 2023/1/920:47*/public class TestStatic
{@Testpublic void test() {CalculatorStaticProxy calculatorStaticProxy = new CalculatorStaticProxy(new CalculatorImpl());calculatorStaticProxy.add(1,2);calculatorStaticProxy.div(8,6);calculatorStaticProxy.mul(10,2);calculatorStaticProxy.sub(20,10);}
}
静态的代理缺点:实现了解耦,但由于代码写死,完全不具备任何灵活性,就日志功能来说,将来其他地方需要附加日志,还得声明更多的静态代理类,就会产生大量的重复代码,日志功能分散,没法统一管理。
2.动态代理
需求:将日志功能击中到一个代理类,将来任何日志需求,都通过一个代理类来实现,这就需要使用动态代理技术。
package com.fjp.spring.proxy;import java.lang.reflect.*;
import java.util.Arrays;/*** @Author: * @Date: 2023/1/921:07*/
public class ProxyFactory {private Object target;public ProxyFactory(Object target) {this.target = target;}public Object getProxy(){//创建一个代理实例,通过反射获取//ClassLoader loader,类加载器,动态代理,指定加载动态生成的代理类的类加载器(根类加载器,扩展类加载器,应用类加载器)//Class<?>[] interfaces,获取目标对象实现的所有接口的class对象的数组//InvocationHandler h,设置代理类的抽象方法如何重写ClassLoader classLoader = this.getClass().getClassLoader();//本类的类加载器Class<?>[] interfaces = target.getClass().getInterfaces();//获取class对象的数组InvocationHandler invocationHandler = new InvocationHandler() {@Override//proxy表示代理对象,method执行的方法,args执行的方法的参数列表public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {Object result=null;try {System.out.println("日志,方法"+method.getName()+",参数"+ Arrays.toString(args));result= method.invoke(target, args);System.out.println("日志,方法"+method.getName()+",结果"+ result);return result ;} catch (Exception e) {e.printStackTrace();System.out.println("日志,方法"+method.getName()+",异常"+ e);} finally {System.out.println("日志,方法"+method.getName()+",方法执行完毕");}return result;}};return Proxy.newProxyInstance(classLoader,interfaces,invocationHandler);}
}
package com.fjp.proxy;import com.fjp.spring.proxy.Calculator;
import com.fjp.spring.proxy.ProxyFactory;
import com.fjp.spring.proxy.impl.CalculatorImpl;
import org.junit.Test;/*** @Author:* @Date: 2023/1/921:25*/
public class ProxyActivity {@Testpublic void Test(){ProxyFactory proxyFactory = new ProxyFactory(new CalculatorImpl());Calculator proxy = (Calculator) proxyFactory.getProxy();proxy.add(1,2);proxy.sub(5,2);proxy.div(2,2);proxy.mul(10,5);}
}
动态代理有两种:
1.jdk动态代理:要求必须有接口,最终生成的代理类和目标对象实现相同的接口在com.sun.proxy包下,类名为$proxy2。
2.cglib动态代理:最终生成的代理类会继承目标类,并且和目标类在相同的包下。
3.3AOP概述
AOP是一种设计思想,是软件设计领域的面向切面编程,他是面向对象编程的一种完善和补充,它以通过编译的方式和运行期动态代理方式实现在不修改源代码的情况下给程序动态统一添加额外功能的技术
3.3.1相关术语
-
横切关注点
从每个方法中抽取出来的同一类非核心业务,在同一个项目中,我们可以使用多个横切关注点对相关方法进行多个不同方向的增强
这个概念不是语法层面天然存在的,而是根据附加功能的逻辑上的需要:有十个附加功能,就有十个横切关注点
-
通知
每一个横切关注点(非核心业务代码)上要做的事情都需要写一个方法来实现,这样的方法就叫做通知方法
前置通知:在被代理的目标方法前执行
返回通知:再被代理的目标方法成功结束后执行
异常通知:在被代理的目标方法异常结束后执行
后置通知:在被代理的目标方法最终结束后执行
环绕通知:使用try…catch…finally结构围绕整个被代理的目标方法,包括上面四种通知对应所有位置
-
切面
封装通知方法的类
-
目标
被代理的目标对象
-
代理
向目标对象应用通知的之后创建的代理对象
-
连接点
这也是一个纯逻辑概念,不是语法定义
把方法排成一排,每一个横切位置看成x轴方向,把方法从上到下执行的顺序看成y轴,x轴和y轴的交叉点就是连接点
-
切入点
定位连接点的方式。
每个类的方法中都包含多个连接点,所以连接点是类中客观存在的事物
如果把连接点看作数据库中的记录,那么切入点就是查询记录的sql语句
spring的AOP技术可以通过切入点定位到特定的连接点
切点通过org.springframework.aop.Pointcut接口进行描述,它使用类和方法作为连接点的查询条件
3.3.2作用
- 简化代码:把方法中固定位置的重复的代码抽取出来,让被抽取的方法更专注于自己的核心功能,
提高内聚性。 - 代码增强:把特定的功能封装到切面类中,看哪里有需要,就往上套,被套用了切面逻辑的方法就
被切面给增强了
3.4基于注解的AOP
3.4.1技术说明
- 动态代理(InvocationHandler):JDK原生的实现方式,需要被代理的目标类必须实现接口。因
为这个技术要求代理对象和目标对象实现同样的接口(兄弟两个拜把子模式)。 - cglib:通过继承被代理的目标类(认干爹模式)实现代理,所以不需要目标类实现接口。
- AspectJ:本质上是静态代理,将代理逻辑“织入”被代理的目标类编译得到的字节码文件,所以最
终效果是动态的。weaver就是织入器。Spring只是借用了AspectJ中的注解。
3.4.2 准备工作
1.添加依赖
在IOC所需依赖基础上再加入下面依赖即可
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"><modelVersion>4.0.0</modelVersion><groupId>com.fjp.spring</groupId><artifactId>spring-aop</artifactId><version>1.0-SNAPSHOT</version><packaging>jar</packaging><properties><maven.compiler.source>8</maven.compiler.source><maven.compiler.target>8</maven.compiler.target><project.build.sourceEncoding>UTF-8</project.build.sourceEncoding></properties><dependencies><!-- 基于Maven依赖传递性,导入spring-context依赖即可导入当前所需所有jar包 --><dependency><groupId>org.springframework</groupId><artifactId>spring-context</artifactId><version>5.3.1</version></dependency><!-- junit测试 --><dependency><groupId>junit</groupId><artifactId>junit</artifactId><version>4.12</version><scope>test</scope></dependency><!-- MySQL驱动 --><dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId><version>5.1.49</version></dependency><!-- 数据源 --><dependency><groupId>com.alibaba</groupId><artifactId>druid</artifactId><version>1.0.31</version></dependency><!-- spring-aspects会帮我们传递过来aspectjweaver --><dependency><groupId>org.springframework</groupId><artifactId>spring-aspects</artifactId><version>5.3.1</version></dependency></dependencies>
</project>
2.接口和实现类
package com.fjp.spring.aop.annotation;/*** @version jdk1.8* @Author:* @Date: 2022/12/31/19:42* @Description: 致敬*/
@SuppressWarnings("all")
public interface Calculator {/*** 加减乘除* @param i* @param j* @return*/int add(int i,int j);int sub(int i,int j);int mul(int i,int j);int div(int i,int j);
}
package com.fjp.spring.aop.annotation;/*** @version jdk1.8* @Author: * @Date: 2022/12/31/19:51* @Description: 致敬*/public class CalculatorImpl implements Calculator {@Overridepublic int add(int i, int j) {int result=i+j;System.out.println("方法内部的 result"+ result);return result;}@Overridepublic int sub(int i, int j) {int result=i-j;System.out.println("方法内部的 result"+ result);return result;}@Overridepublic int mul(int i, int j) {int result=i*j;System.out.println("方法内部的 result"+ result);return result;}@Overridepublic int div(int i, int j) {int result=i/j;System.out.println("方法内部的 result"+ result);return result;}
}
3.4.3 创建切面类并配置
各种通知的执行顺序:
Spring版本5.3.x以前:
前置通知
目标操作
后置通知
返回通知或异常通知
Spring版本5.3.x以后:
前置通知
目标操作
返回通知或异常通知
后置通知
package com.fjp.spring.aop.annotation;import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;import java.util.Arrays;/*** @Author: * @Date: 2023/1/9 23:42* 切面中需要通过指定的注解将方法表示为通知方法* @before:前置通知,在目标方法之前执行* @After:后置通知在finally内执行* @AfterReturning:返回通知,在目标对象方法返回值之后执行* @AfterThrowing:异常通知,在目标对象方法的catch子句中执行*** 2.切入点表达式需要设置在标识通知的注解value属性中* Before("execution(* com.fjp.spring.aop.annotation.CalculatorImpl.*(..))")//前置通知* 第一个*表示任意的访问修饰符和返回值类型* 第二个*表示当前类中任意的方法* ..表示任意的参数列表* 类的地方也可以使用*,表示包下所有的类** 3.获取连接点信息* 在通知方法的参数位置,设置JoinPoint类型的参数,既可以获取连接点对应方法的信息* //获取连接点所对应的方法的签名信息* String methodName = joinPoint.getSignature().getName();* //获取连接点所对应的参数* Object[] args = joinPoint.getArgs();****4. //切入点表达式的重用* @Pointcut("execution(* com.fjp.spring.aop.annotation.CalculatorImpl.*(..))")* public void pointCut(){}* 使用方式:@After("pointCut()")***/
@Component
@Aspect//将当前组件标识为切面
public class LoggerAspect {//切入点表达式的重用@Pointcut("execution(* com.fjp.spring.aop.annotation.CalculatorImpl.*(..))")public void pointCut(){}//抽取横切关注点,将横切关注点封装为一个方法@Before("execution(* com.fjp.spring.aop.annotation.CalculatorImpl.*(..))")//前置通知public void beforeAdviceMethod(JoinPoint joinPoint){//获取连接点所对应的方法的签名信息String methodName = joinPoint.getSignature().getName();//获取连接点所对应的参数Object[] args = joinPoint.getArgs();System.out.println("LoggerAspect,方法:"+methodName+"参数:"+ Arrays.toString(args)+"前置通知");}@After("pointCut()")public void afterAdviceMethod(JoinPoint joinPoint){String methodName = joinPoint.getSignature().getName();System.out.println("方法:"+methodName+"执行完毕"+"后置通知");}/**再返回通知中,若要获取目标对象方法的返回值,只需要 @AfterReturning注解放入returning属性就可以将通知方法的某个参数,指定为接收目标对象方法返回值的参数*/@AfterReturning(value = "pointCut()",returning = "result")public void afterReturningAdviceMethod(JoinPoint joinPoint,Object result){String methodName = joinPoint.getSignature().getName();//返回值之后执行System.out.println("方法:"+methodName+"结果:"+result+"返回通知");}/***在异常通知中若要获取目标对象方法的异常* 只需要通过@AfterThrowing注解的throwing属性* 就可以将通知方法的某个参数,指定为接收目标对象方法异常的参数**/@AfterThrowing(value = "pointCut()",throwing = "ex")public void afterThrowingAdviceMethod(JoinPoint joinPoint,Exception ex){String methodName = joinPoint.getSignature().getName();System.out.println("方法:"+methodName+"异常:"+ex+"异常通知");}//环绕通知@Around("pointCut()")public Object aroundAdviceMethod(ProceedingJoinPoint joinPoint){Object result=null;try {System.out.println("环绕通知--前置通知");//目标方法的执行result=joinPoint.proceed();System.out.println("环绕通知--返回通知");} catch (Throwable e) {e.printStackTrace();System.out.println("环绕通知--异常通知");}finally {System.out.println("环绕通知--后置通知");}return result;}
}
package com.fjp.spring.test;import com.fjp.spring.aop.annotation.Calculator;
import com.fjp.spring.aop.annotation.CalculatorImpl;
import com.fjp.spring.aop.annotation.LoggerAspect;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;/*** @Author: * @Date: 2023/1/10 20:45*/
public class AOPTest {@Testpublic void testAOPAnnotation(){ApplicationContext ioc= new ClassPathXmlApplicationContext("aop-annotation.xml");Calculator bean = ioc.getBean(Calculator.class);bean.sub(10,4);}
}
3.4.4AOP操作(基于Aspect注解)
配置文件
<dependency><groupId>org.aspectj</groupId><artifactId>aspectjweaver</artifactId><version>1.8.9</version>
</dependency>
<!-- 数据源 -->
<dependency><groupId>com.alibaba</groupId><artifactId>druid</artifactId><version>1.2.8</version>
</dependency><!-- spring-aspects会帮我们传递过来aspectjweaver -->
<dependency><groupId>org.springframework</groupId><artifactId>spring-aspects</artifactId><version>5.1.17.RELEASE</version>
</dependency>
<dependency><groupId>org.springframework</groupId><artifactId>spring-context</artifactId><version>5.3.6</version>
</dependency>
第一步:创建实体类,并在实体类中创建方法
public class User {private int id;private String username;private String password;public void findAll(){System.out.println(username+","+password);}
}
第二步:创建增强类,编写增强方法
public class UserPoxy {public void before(){System.out.println("before");}
}
第三步,进行通知的配置
(创建配置文件)
<?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"xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsdhttp://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsdhttp://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd"><!--开启注解扫描--><context:component-scan base-package="com.fjp.AOPano"></context:component-scan>
<!--开启Aspect 生成代理对象--><aop:aspectj-autoproxy></aop:aspectj-autoproxy>
</beans>
(2)使用注解Component创建User和UserProxy对象
![外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传](https://img-home.cs
(3)在增强类上添加Aspect注解
(4)在Spring文件中生成代理对象
<!--开启Aspect 生成代理对象--><aop:aspectj-autoproxy></aop:aspectj-autoproxy>
第四步,配置通知类型
package com.fjp.AOPano;import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;@Component
@Aspect //生成代理对象
public class UserPoxy {//作为前置通知@Before("execution(* com.fjp.AOPano.User.findAll(..))")public void before(){System.out.println("before");}//最终通知@After("execution(* com.fjp.AOPano.User.findAll(..))")public void after() {System.out.println("after");}@AfterReturning("execution(* com.fjp.AOPano.User.findAll(..))")//在执行方法返回值之后输出public void afterReturning() {System.out.println("afterReturning");}@AfterThrowing("execution(* com.fjp.AOPano.User.findAll(..))")public void afterThrowing() {System.out.println("afterThrowing");}//环绕通知@Around("execution(* com.fjp.AOPano.User.findAll(..))")public void arround(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {System.out.println("环绕前");//被增强方法proceedingJoinPoint.proceed();System.out.println("环绕后");}}
第五步重用切入点
@Component
@Aspect //生成代理对象
public class UserPoxy {@Pointcut("execution(* com.fjp.AOPano.User.findAll(..))")public void pointCut(){}//作为前置通知@Before("pointCut()")public void before(){System.out.println("before");}//最终通知@After("pointCut()")public void after() {System.out.println("after");}@AfterReturning("pointCut()")//在执行方法返回值之后输出public void afterReturning() {System.out.println("afterReturning");}@AfterThrowing("pointCut()")public void afterThrowing() {System.out.println("afterThrowing");}//环绕通知@Around("pointCut()")public void arround(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {System.out.println("环绕前");//被增强方法proceedingJoinPoint.proceed();System.out.println("环绕后");}}
3.4.5切面的优先级
package com.fjp.spring.aop.annotation;import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;/*** @Author: * @Date: 2023/1/10 22:10*/
@Component
@Aspect
@Order(1)//通过Order注解设置一个数值,来设置优先级,默认值为Integer的最大值,值越小优先级越高
public class ValidateAspect {//@Before("execution(* com.fjp.spring.aop.annotation.CalculatorImpl.*(..))")@Before("com.fjp.spring.aop.annotation.LoggerAspect.pointCut()")public void beforeMethod(){System.out.println("ValidateAspect--前置通知");}
}
3.4.6 基于配置文件(AspectJ)
1.创建增强类和被增强类
public class Student {public void add(){System.out.println("Student的add方法");}
}
public class StudentProxy {public void before(){System.out.println("before");}public void after() {System.out.println("after");}public void around(ProceedingJoinPoint p) throws Throwable {System.out.println("around before");p.proceed();System.out.println("around after");}
}
2.在spring配置文件中创建两个类对象
<bean class="com.fjp.AOPXml.Student" id="student"></bean>
<bean class="com.fjp.AOPXml.StudentProxy" id="studentProxy"></bean>
3.在配置文件中配置切入点
<!--配置AOP增强切入点--><aop:config>
<!--切入点--><aop:pointcut id="stu" expression="execution(* com.fjp.AOPXml.Student.add(..))"/>
<!-- 配置切面--><aop:aspect ref="studentProxy">
<!-- 增强作用在具体的方法上--><aop:before method="before" pointcut-ref="stu"></aop:before><aop:after method="after" pointcut-ref="stu"></aop:after><aop:around method="around" pointcut-ref="stu"></aop:around></aop:aspect></aop:config>
3.4.7 完全注解开发
@Configuration
@ComponentScan(basePackages = {"com.fjp"})
@EnableAspectJAutoProxy(proxyTargetClass = true)
public class BookConfig {
}
3.5 jdbcTemplate
3.5.1 jdbcTemplate
简介:Spring框架对JDBC进行封装,使用JdbcTemolate方便实现对数据库操作
3.5.2准备工作
1.引入依赖
<dependency><groupId>com.alibaba</groupId><artifactId>druid</artifactId><version>1.1.6</version> </dependency> <dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId><version>8.0.19</version> </dependency> <dependency><groupId>org.springframework</groupId><artifactId>spring-tx</artifactId><version>5.2.10.RELEASE</version> </dependency> <dependency><groupId>org.springframework</groupId><artifactId>spring-jdbc</artifactId><version>6.0.0</version> </dependency> <dependency><groupId>org.springframework</groupId><artifactId>spring-orm</artifactId><version>5.1.18.RELEASE</version> </dependency>
2.在spring配置文件中配置连接池
<context:property-placeholder location="db.properties"></context:property-placeholder> <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource" destroy-method="close"><property name="url" value="${jdbc.url}"></property><property name="driver" value="${jdbc.driver}"></property><property name="username" value="${jdbc.username}"></property><property name="password" value="${jdbc.password}"></property> </bean>
jdbc.url=jdbc:mysql://localhost:3306/mybatis?serverTimezone=UTC jdbc.driver=com.mysql.cj.jdbc.Driver jdbc.username=root jdbc.password=123456
3.配置jdbcTemplate对象,注入Dataource
<!-- jdbcTemplate--><bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate"> <!--注入数据库连接池配置--><property name="dataSource" ref="dataSource"></property></bean>
4.创建service类,dao类,在dao内部注入jdbcTemplate对象,实现操作
@Service public class PersonService {//注入dao@Autowiredprivate PersonDao personDao; }
@Repository public class PersonDaoImpl implements PersonDao {// 注入jdbatemplate@Autowiredprivate JdbcTemplate jdbcTemplate; }
public interface PersonDao { }
<!-- 组件扫描--><context:component-scan base-package="com.fjp"></context:component-scan>
3.5.3 JdbcTemplate操作数据库
1.对应数据库表创建实体类
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Person {private int id;private String name;private int age;private String tel;private String addr;private double score;
}
4.事务
二、MyBatis
1.MyBatis简介
MyBatis是一个基于java的持久层框架
1.2MYBatis特性
- mybatis是支持定制化sql,存储过程以及高级映射的优秀的持久层框架
- mybatis避免了几乎所有的JDBC代码和手动设置参数以及获取结果集
- mybatis可以使用简单的XML或者注解用于配置和原始映射,将接口和java的pojo(普通的java对象)映射成数据库中的记录
- mybatis是一个半自动的ORM框架
1.3和其他持久化技术对比
-
JDBC
SQL夹杂在java代码中偶合度高,导致硬编码内伤
维护不易且实际开发需求中SQL有变化,频繁修改的情况多见
代码冗长,开发效率低
-
Hibermate和JPA
操作简便,开发效率高
程序中的长难复杂SQL需要绕过框架
基于全映射的全自动框架,大量字段的POJO进行部分映射时比较困难
反射操作太多,导致数据库性能下降
-
MyBatis
轻量级,性能出色
SQL和java编码分开,功能便捷清晰,java代码专注与业务,SQL语句专注于数据
开发效率稍逊与Hibermate,但是完全能够接受。
注意事项:
MySQL不同版本的注意事项
1、驱动类driver-class-name MySQL 5版本使用jdbc5驱动,驱动类使用:com.mysql.jdbc.Driver
MySQL 8版本使用jdbc8驱动,驱动类使用:com.mysql.cj.jdbc.Driver
2、连接地址url MySQL 5版本的url: jdbc:mysql://localhost:3306/ssm
MySQL 8版本的url: jdbc:mysql://localhost:3306/ssm?serverTimezone=UTC
否则运行测试用例报告如下错误: java.sql.SQLException: The server time zone value ‘Öйú±ê׼ʱ¼ä’ is unrecognized or represents more
1.4搭建MyBatis
1.创建maven
<dependencies>
<!-- Mybatis核心 -->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.5.10</version>
</dependency>
<!-- junit测试 -->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>
<!-- MySQL驱动 -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.6</version>
</dependency>
</dependencies>
2.创建MyBatis核心配置文件
习惯上命名为mybatis-config.xml
核心配置文件主要用于配置连接数据库的环境以及MyBatis的全局配置信息
核心配置文件存放的位置是src/main/resources目录下
<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/ssm?serverTimezone=UTC"/><property name="username" value="root"/><property name="password" value="19480204"/></dataSource></environment></environments>
<!--引入mybatis的映射文件--><mappers><mapper resource="mappers/UserMapper.xml"/></mappers>
</configuration>
3.创建Mapper接口
MyBatis中的mapper接口相当于以前的dao。但是区别在于,mapper仅仅是接口,我们不需要 提供实现类
public interface UserMapper {/*** 添加*/int insertUser();
}
4.创建MyBatis的映射文件
相关概念:ORM(Object Relationship Mapping)对象关系映射。
对象:Java的实体类对象
关系:关系型数据库
映射:二者之间的对应关系
1、映射文件的命名规则:
表所对应的实体类的类名+Mapper.xml 例如:表t_user,映射的实体类为User,所对应的映射文件为UserMapper.xml 因此一个映射文件对应一个实体类,对应一张表的操作 MyBatis映射文件用于编写SQL,访问以及操作表中的数据 MyBatis映射文件存放的位置是src/main/resources/mappers目录下
2、 MyBatis中可以面向接口操作数据,要保证两个一致:
a>mapper接口的全类名和映射文件的命名空间(namespace)保持一致
b>mapper接口中方法的方法名和映射文件中编写SQL的标签的id属性保持一致
<!--namespace地址为要映射的mapper接口地址-->
<mapper namespace="com.fjp.mybatis.mapper.UserMapper">
<!--mapper接口和映射文件要保证两个一致mapper接口的全类名要和映射文件的namespace一致mapper接口中的方法名要和映射文件的sql的id保持一致
-->
<!--int insertUser();--><insert id="insertUser">insert into t_user values (null,'admin','12346',8,'男','admin@163.com')</insert>
</mapper>
5.测试
public class MyBatisTest {@Testpublic void testInsert() throws IOException {//获取核心配置文件的输入流InputStream resourceAsStream = Resources.getResourceAsStream("mybatis-config.xml");//获取sqlSessionFactoryBuilder对象SqlSessionFactoryBuilder sqlSessionFactoryBuilder = new SqlSessionFactoryBuilder();//获取SQLSessionFactory对象SqlSessionFactory build = sqlSessionFactoryBuilder.build(resourceAsStream);//获取sql的会话对象SqlSession,是mybatis提供的操作数据库的对象SqlSession sqlSession = build.openSession();//获取UserMapper的代理实现类对象UserMapper mapper = sqlSession.getMapper(UserMapper.class);//调用mapper中的方法int i = mapper.insertUser();System.out.println("结果;"+i);//提交事务sqlSession.commit();sqlSession.close();}
}
SqlSession:代表Java程序和数据库之间的会话。(HttpSession是Java程序和浏览器之间的 会话)
SqlSessionFactory:是“生产”SqlSession的“工厂”。
工厂模式:如果创建某一个对象,使用的过程基本固定,那么我们就可以把创建这个对象的 相关代码封装到一个“工厂类”中,以后都使用这个工厂类来“生产”我们需要的对象。
6.加入log4j日志功
在pom.xml加入依赖
<!-- log4j日志 -->
<dependency><groupId>log4j</groupId><artifactId>log4j</artifactId><version>1.2.12</version>
</dependency>
创建log4j.xml
<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>
<!-- resultType 设置结果类型,即查询的数据要转换为的java类型resultMap 设置自定义映射,处理多对一,一对多的映射关系-->
7.增删查改
public interface UserMapper {/*** 添加*/int insertUser();/*** 修改* @return*/int updateUser();/*** 删除*/void deleteUser();/***根据id查询*/User getUserById();/*** 查询所有的用户* @return*/List<User> getUser();
}
<insert id="insertUser">insert into t_user values (null,'admin','12346',8,'男','admin@163.com')
</insert><update id="updateUser">update t_user set username='lytong',password='123' where id=1;
</update>
<delete id="deleteUser">delete from t_user where id=2;
</delete><select id="getUserById" resultType="com.fjp.mybatis.pojo.User">select * from t_user where id=1;
</select><select id="getUser" resultType="com.fjp.mybatis.pojo.User">select * from t_user;
</select>
@Testpublic void TestUpdate(){SqlSession sqlSession = SqlSessionUtil.getSqlSession();UserMapper mapper = sqlSession.getMapper(UserMapper.class);int i = mapper.updateUser();sqlSession.close();}@Testpublic void Testdelete(){SqlSession sqlSession = SqlSessionUtil.getSqlSession();UserMapper mapper = sqlSession.getMapper(UserMapper.class);mapper.deleteUser();sqlSession.close();}@Testpublic void TestGetUserById(){SqlSession sqlSession = SqlSessionUtil.getSqlSession();UserMapper mapper = sqlSession.getMapper(UserMapper.class);User user = mapper.getUserById();System.out.println(user);}@Testpublic void TestGeyUser(){SqlSession sqlSession = SqlSessionUtil.getSqlSession();UserMapper mapper = sqlSession.getMapper(UserMapper.class);List<User> user = mapper.getUser();user.forEach(System.out::println);}
8.mybatis配置文件
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configurationPUBLIC "-//mybatis.org//DTD Config 3.0//EN""http://mybatis.org/dtd/mybatis-3-config.dtd">
<!--MyBatis核心配置文件中,标签的顺序:properties?,settings?,typeAliases?,typeHandlers?,objectFactory?,objectWrapperFactory?,reflectorFactory?,plugins?,environments?,databaseIdProvider?,mappers?
-->
<configuration><!--引入properties,就可以在当前文件中使用${key}的方式来访问value--><properties resource="mappers/jdbc.properties"/><!--typeAliases设置类型别名,为具体的类型设置别名在mybatis的范围内,就可以使用一个别名表示具体类型type:表示需要起别名的类型alias:表示设置的别名--><typeAliases><!--<typeAlias type="com.fjp.mybatis.pojo.User" alias="abcd"></typeAlias>--><!--可以不写alias,这样默认别名就是他的类名,且不区分大小写--><!--<typeAlias type="com.fjp.mybatis.pojo.User"></typeAlias>--><!--通过包设置类型别名,指定包下所有的类型将全部拥有默认的别名,即类名且不区分大小写--><package name="com.fjp.mybatis.pojo"/></typeAliases><!--配置连接数据库的环境-->
<!--environments:配置连接数据库的环境属性:default设置默认使用的环境的id
--><environments default="development">
<!--environment:设置一个具体的连接数据库的环境属性:id:设置环境的唯一标识符,不能重复。
--><environment id="development">
<!--数据事务管理器属性:type:设置事务管理的方式type=“jdbc” 表示使用jdbc中原生的事务管理方式type=“MANAGED” 表示被管理的
--><transactionManager type="JDBC"/>
<!--dataSource设置数据源属性:type:设置数据源的类型type=“POOLED/UNPOOLED/JNDI”POOLED:表示使用数据库连接池UNPOOLED:不使用数据库连接池JNDI:表示使用上下文中的数据源
--><dataSource type="POOLED"><property name="driver" value="${jdbc.driver}"/><property name="url" value="${jdbc.url}"/><property name="username" value="${jdbc.root}"/><property name="password" value="${jdbc.password}"/></dataSource></environment></environments><!--引入mybatis的映射文件--><mappers><!--<mapper resource="mappers/UserMapper.xml"/>以包的方式引入映射文件,但是必须满足:1.mapper接口和映射文件所在的包必须一致2.mapper接口的名字和映射文件的名字必须一致--><package name="com.fjp.mybatis.mapper"/></mappers>
</configuration>
9.mybatis获取参数的两种方式
mybatis获取参数值的两种方式:${}和#{}
${}的本质就是字符串拼接,#{}的本质就是占位符赋值
${}使用字符串拼接的方式拼接sql,若为字符串类型或日期类型的字段进行赋值时,需要手动加引号
#{}使用占位符赋值的方式拼接sql,此时为字符串或日期类型的字段进行赋值,可以自动添加单引号
9.1 单个字面量类型的参数
若mapper接口中的方法参数为单个的字面量类型
此时可以使用KaTeX parse error: Expected 'EOF', got '#' at position 4: {}和#̲{}以任意的名称获取参数的值,…{}需要手动加单引号
public interface UserMapper {/*** 根据用户名查询用户信息* @param username* @return*/User getUserByUsername(String username);
}
<!-- User getUserByUsername(String username);--><select id="getUserByUsername" resultType="User">
<!-- select * from t_user where username= #{username}-->select * from t_user where username= '${username}'</select>
@Test
public void testGetUserByUsername(){SqlSession sqlSession = SqlSessionUtil.getSqlSession();UserMapper mapper = sqlSession.getMapper(UserMapper.class);User user = mapper.getUserByUsername("admin");System.out.println(user);}
9.2、多个字面量类型的参数
若mapper接口中的方法参数为多个时
此时MyBatis会自动将这些参数放在一个map集合中,以arg0,arg1…为键,
以参数为值;以 param1,param2…为键,以参数为值;
因此只需要通过${}和#{}访问map集合的键就可以获取相 对应的 值,
注意:${}需要手动加单引号
/*** 根据用户名和密码验证登录* @return*/
User checkLogin(String username,String password);
<select id="checkLogin" resultType="User">
<!-- select * from t_user where username=#{arg0} and password=#{arg1}-->select * from t_user where username='${param1}'and password='${param2}'</select>
@Testpublic void testLogin(){SqlSession sqlSession = SqlSessionUtil.getSqlSession();UserMapper mapper = sqlSession.getMapper(UserMapper.class);User user = mapper.checkLogin("admin", "12346");System.out.println(user);}
9.3、map集合类型的参数
若mapper接口中的方法需要的参数为多个时,此时可以手动创建map集合,
将这些数据放在 map中 只需要通过${}和#{}访问map集合的键就可以获取相对应的值
注意:${}需要手动加单引号
/***以map集合作为参数,验证登录* @param map* @return*/
User checkLoginByMap(Map<String,Object> map);
<!-- User checkLoginByMap(Map<String,Object> map);--><select id="checkLoginByMap" resultType="user">select * from t_user where username=#{username} and password=#{password}</select>
@Testpublic void testMap(){SqlSession sqlSession = SqlSessionUtil.getSqlSession();UserMapper mapper = sqlSession.getMapper(UserMapper.class);Map<String, Object> map = new HashMap<>();map.put("username","admin");map.put("password","12346");User user = mapper.checkLoginByMap(map);System.out.println(user);}
9.4实体类类型的参数
若mapper接口中的方法参数为实体类对象时 此时可以使用${}和#{}
通过访问实体类对象中的属性名获取属性值,注意${}需要手动加单引号
/*** 添加用户信息(实体类的方式)* @param user*/
void insertUser(User user);
<!--void insertUser(User user);--><insert id="insertUser">insert into t_user values(null,#{username},#{password},#{age},#{gender},#{email})</insert>
@Testpublic void testInsert(){SqlSession sqlSession = SqlSessionUtil.getSqlSession();UserMapper mapper = sqlSession.getMapper(UserMapper.class);mapper.insertUser(new User(null,"李怡潼","19740117",18,"女","lyt@163.com"));}
9.5使用@Param标识参数
可以通过@Param注解标识mapper接口中的方法参数 此时,会将这些参数放在map集合中
以@Param注解的value属性值为键,以参数为值;
以 param1,param2…为键,以参数为值;
只需要通过${}和#{}访问map集合的键就可以获取相对应 的值,
注意:${}需要手动加单引号
/*** 验证登录,使用@param* @return*/
User checkLoginByParam(@Param("username") String username, @Param("password") String password);
<select id="checkLoginByParam" resultType="User">select * from t_user where username=#{username} and password=#{password}
</select>
@Test
public void testParam(){SqlSession sqlSession = SqlSessionUtil.getSqlSession();UserMapper mapper = sqlSession.getMapper(UserMapper.class);User user = mapper.checkLoginByParam("李怡潼", "19740117");System.out.println(user);
}
1.5.mybatis的查询功能
1.查询一个实体类对象
当查询的数据为多条时,不能使用实体类作为返回值,否则会抛出异常 TooManyResultsException;
但是若查询的数据只有一条,可以使用实体类或集合作为返回值
/*** 通过id查询* @param id* @return*/
User getUserById(@Param("id") Integer id);
<select id="getUserById" resultType="User">select * from t_user where id=#{id}
</select>
@Test
public void testGetUserById(){SqlSession sqlSession = SqlSessionUtil.getSqlSession();SelectMapper mapper = sqlSession.getMapper(SelectMapper.class);User userById = mapper.getUserById(8);System.out.println(userById);
}
2.查询一个list集合
/*** list集合查询所有的用户信息* @return*/
List<User> getUserList();
<select id="getUserList" resultType="User">select * from t_user
</select>
@Test
public void testGetUserList(){SqlSession sqlSession = SqlSessionUtil.getSqlSession();SelectMapper mapper = sqlSession.getMapper(SelectMapper.class);List<User> userList = mapper.getUserList();for (User user : userList) {System.out.println(user);}
}
3.查询单个数据
/*** 查询用户的总数* @return*/
Integer getCount();
<select id="getCount" resultType="Integer">select count(*) from t_user
</select>
@Test
public void testGetCount(){SqlSession sqlSession = SqlSessionUtil.getSqlSession();SelectMapper mapper = sqlSession.getMapper(SelectMapper.class);Integer count = mapper.getCount();System.out.println(count);
}
4.查询一条数据为map集合
/*** 通过id查询map集合的的数据* @return*/
Map<String,Object> getUserId(@Param("id")Integer id);
<!--Map<String,Object> getUserId(@Param("id")Integer id);--><select id="getUserId" resultType="map">select * from t_user where id=#{id}</select>
@Test
public void testGetUserIdMap(){SqlSession sqlSession = SqlSessionUtil.getSqlSession();SelectMapper mapper = sqlSession.getMapper(SelectMapper.class);Map<String, Object> userId = mapper.getUserId(6);System.out.println(userId);
}
5.查询多条数据为map集合
要将查询的Map集合放到List集合里,也可以通过注解进行查询
/*** 查询所有的用户信息为一个map集合(多条数据)* @return* List<Map<String,Object>> getAllUserToMap();*/
@MapKey("id")
Map<String,Object> getAllUserToMap();
<!--Map<String,Object> getAllUserToMap();--><select id="getAllUserToMap" resultType="map">select * from t_user</select>
@Testpublic void testAllUserToMap(){SqlSession sqlSession = SqlSessionUtil.getSqlSession();SelectMapper mapper = sqlSession.getMapper(SelectMapper.class);
// List<Map<String, Object>> allUserToMap = mapper.getAllUserToMap();
// System.out.println(allUserToMap);Map<String, Object> allUserToMap = mapper.getAllUserToMap();System.out.println(allUserToMap);}
1.6 特殊sql的执行
1.模糊查询
/*** 模糊查询* @param mohu* @return*/
List<User> getUserByLike(@Param("mohu")String mohu);
<!--List<User> getUserByLike(@Param("mohu") String mohu); --><select id="getUserByLike" resultType="User">
<!--SELECT * FROM t_user where username LIKE '%${mohu}%'-->
<!-- SELECT * FROM t_user where username LIKE CONCAT('%',#{mohu},'%')-->SELECT * FROM t_user where username LIKE "%" #{mohu}"%"(建议使用)</select>
@Test
public void testSpecial(){SqlSession sqlSession = SqlSessionUtil.getSqlSession();SpecialSQLMapper mapper = sqlSession.getMapper(SpecialSQLMapper.class);List<User> a = mapper.getUserByLike("a");a.forEach( System.out::println);
}
2.批量删除
/*** 批量删除* @param ids*/void deleteMoreUser(@Param("ids") String ids);
<!-- void deleteMoreUser(@Param("ids") String ids);--><delete id="deleteMoreUser">delete from t_user where id in(${ids})</delete>
@Testpublic void testDeleteMoveUser(){SqlSession sqlSession = SqlSessionUtil.getSqlSession();SpecialSQLMapper mapper = sqlSession.getMapper(SpecialSQLMapper.class);mapper.deleteMoreUser("9,10");}
3.动态设置表明
/*** 动态设置表名,查询信息* @param nameTable* @return*/
List<User> getUserList(@Param("nameTable") String nameTable);
<!-- List<User> getUserList(@Param("nameTable") String nameTable);--><select id="getUserList" resultType="User">select * from ${nameTable}</select>
@Test
public void testGetUserList(){SqlSession sqlSession = SqlSessionUtil.getSqlSession();SpecialSQLMapper mapper = sqlSession.getMapper(SpecialSQLMapper.class);List<User> t_user = mapper.getUserList("t_user");System.out.println(t_user);
}
4.添加功能获取自增主键
场景模拟:
t_clazz(clazz_id,clazz_name)
t_student(student_id,student_name,clazz_id)
1、添加班级信息
2、获取新添加的班级的id
3、为班级分配学生,即将某学的班级id修改为新添加的班级的id
/*** 添加用户信息并获取自增的主键* @param user*/
void insertUser(User user);
<!-- oid insertUser(User user);useGeneratedKeys:设置使用自增的主键
* keyProperty:因为增删改有统一的返回值是受影响的行数,因此只能将获取的自增的主键放在传输的参
数user对象的某个属性中
--><insert id="insertUser" useGeneratedKeys="true" keyProperty="id">insert into t_user values(null ,#{username},#{password},#{age},#{gender},#{email})</insert>
@Test
public void testInsertUser(){SqlSession sqlSession = SqlSessionUtil.getSqlSession();SpecialSQLMapper mapper = sqlSession.getMapper(SpecialSQLMapper.class);User user = new User(null, "李大头", "123", 12, "女", "123@qq.com");mapper.insertUser(user);System.out.println(user);
}
1.7搭建MyBatis框架
1.自定义映射resultMap
<settings>
<!--将下划线映射为驼峰--><setting name="mapUnderscoreToCamelCase" value="true"/></settings>
1.1普通映射
/*** 根据id查询员工信息* @param empId* @return*/
Emp getEmpByEmpId(@Param("empId") Integer empId);
<!--resiltMap:设置自定义映射关系
id唯一标识
type:处理映射关系的实体类的类型
常用标签:
id:处理主键和实体类中实现的映射关系
result:处理普通字段和实体类中实现的映射关系
column:设置映射关系中的字段名必须是sql的某个字段名
property:设置映射关系中的属性的属性名,必须是处理的实体类类型中的属性名
-->
<resultMap id="empResultMap" type="Emp"><id column="emp_id" property="empId"></id><result column="emp_name" property="empName"></result><result column="age" property="age"></result><result column="gender" property="gender"></result>
</resultMap>
<!-- Emp getEmpByEmpId(@Param("empId") Integer empId);-->
<select id="getEmpByEmpId" resultMap="empResultMap">select * from t_emp where emp_id=#{empId}
</select>
<!--字段名和属性名不一致的情况,如何处理映射关系
1.为查询的字段名设置别名,和属性名保持一致
2.当字段符合mysql的要求使用下划线,而属性符合java的要求使用驼峰此时可以在mybatis的核心配置文件设置一个全局配置,自动将下划线映射为驼峰
-->
<!--方式一:select emp_id empId,emp_name empName,age,gender from t_emp where emp_id=#{empId}--><select id="getEmpByEmpIdOld" resultType="Emp">select * from t_emp where emp_id=#{empId}</select>
@Test
public void testGetEmpByEmpId(){SqlSession sqlSession = SqlSessionUtil.getSqlSession();EmpMapper mapper = sqlSession.getMapper(EmpMapper.class);Emp empByEmpId = mapper.getEmpByEmpId(3);System.out.println(empByEmpId);
}
<settings>
<!--将下划线映射为驼峰--><setting name="mapUnderscoreToCamelCase" value="true"/></settings>
1.2多对一的映射关系
/*** 获取员工以及所对应的部门信息* @param empId* @return*/
Emp getEmpAndDeptByEmpId(@Param("empId")Integer empId);
<!-- Emp getEmpAndDeptByEmpId(@Param("empId")Integer empId);--><select id="getEmpAndDeptByEmpId" resultMap="empAndDeptResultMap">selectt_emp.*,t_dept.*from t_empleft join t_depton t_emp.dept_id=t_dept.dept_idwhere t_emp.emp_id=1</select>
1.级联方式处理<resultMap id="empAndDeptResultMap" type="Emp"><id column="emp_id" property="empId"></id><result column="emp_name" property="empName"></result><result column="age" property="age"></result><result column="gender" property="gender"></result><result column="dept_id" property="dept.deptId"></result><result column="dept_name" property="dept.deptName"></result></resultMap>
2.association
<resultMap id="empAndDeptResultMap" type="Emp"><id column="emp_id" property="empId"></id><result column="emp_name" property="empName"></result><result column="age" property="age"></result><result column="gender" property="gender"></result><!--association:处理多对一的映射关系(处理实体类类型的属性)property:设置需要处理的映射关系的属性的属性名javaType:设置要处理的属性的类型
--><association property="dept" javaType="Dept"><id column="dept_id" property="deptId"></id><result column="dept_name" property="deptName"></result></association>
</resultMap>
3.分步查询
/*** 通过分步查询获得员工和部门信息* 第一步* @param empId* @return*/
Emp getEmpAndDeptByStepOne(@Param("empId")Integer empId);
新建mapper接口
/*** 通过分步查询查询员工以及所对应的部门信息* 第二部* @return*/
Dept getEmpAndDeptByStepTwo(@Param("deptId") Integer deptId);
<!-- Emp getEmpAndDeptByStepOne(@Param("empId")Integer empId);--><select id="getEmpAndDeptByStepOne" resultMap="empAndDeptByStepResultMap">select * from t_emp where emp_id = #{empId}</select>
<resultMap id="empAndDeptByStepResultMap" type="Emp"><id column="emp_id" property="empId"></id><result column="emp_name" property="empName"></result><result column="age" property="age"></result><result column="gender" property="gender"></result><!-- property:设置需要处理映射关系的属性的属性名select:设置分步查询的sql的唯一标识colum:将查询出的某个字段设置为分步查询的sql的条件--><association property="dept"select="com.fjp.mybatis.mapper.DeptMapper.getEmpAndDeptByStepTwo"column="dept_id"></association>
</resultMap>
新建mapper对应的mybatis配置文件
<!-- Dept getEmpAndDeptByStepTwo(@Param("deptId") Integer deptId);--><select id="getEmpAndDeptByStepTwo" resultType="Dept">select * from t_dept where dept_id=#{deptId};</select>
1.3延迟加载
分步查询的优点:可以实现延迟加载,但是必须在核心配置文件中设置全局配置信息
lazyLoadingEnable:延迟加载的全局开关,当开启时,所有关联对象都会延迟加载
aggressiveLazyLoading:当开启时,任何方法的调用都会加载该对象的所有属性,否则,每个属性会按需加载
fetchType:在开启了延迟加载的环境中通过该属性设置当前的分布查询是否使用延迟加载eager:立即加载 lazy 延迟加载
--><association property="dept" fetchType="eager"select="com.fjp.mybatis.mapper.DeptMapper.getEmpAndDeptByStepTwo"column="dept_id"></association>
<!-- 开启延时加载--><setting name="lazyLoadingEnabled" value="true"/>
<!-- 按需加载--><setting name="aggressiveLazyLoading" value="false"/>
1.4一对多的映射关系
处理一对多的映射关系:
1.collection
<!--collection 处理一对多的映射关系(处理集合类型的属性)ofType:设置集合类型的属性中存储的数据的类型
--><resultMap id="deptAndEmpResultMap" type="Dept"><id column="dept_id" property="deptId"></id><result column="dept_name" property="deptName"></result><collection property="emps" ofType="Emp"><id column="emp_id" property="empId"></id><result column="emp_name" property="empName"></result><result column="age" property="age"></result><result column="gender" property="gender"></result></collection></resultMap>
<!-- Dept getDeptAndEmpByDeptId(@Param("deptId") Integer deptId);--><select id="getDeptAndEmpByDeptId" resultMap="deptAndEmpResultMap">select t_emp.*,t_dept.* from t_dept left join t_emp on t_dept.dept_id = t_emp.dept_id where t_dept.dept_id=#{deptId}</select>
/*** 查询部门以及部门中的员工信息* @param deptId* @return*/
Dept getDeptAndEmpByDeptId(@Param("deptId") Integer deptId);
@Test
public void testDeptAndEmpByDeptId(){SqlSession sqlSession = SqlSessionUtil.getSqlSession();DeptMapper mapper= sqlSession.getMapper(DeptMapper.class);Dept dept = mapper.getDeptAndEmpByDeptId(1);System.out.println(dept);
}
2.分布查询
/*** 通过分步查询部门以及部门中的员工信息* @param deptId* @return*/
Dept getDeptAndEmpByStepOne(@Param("deptId")Integer deptId);
/*** 分步查询第二步* @param deptId* @return*/
List<Emp> getDeptAndEmpBySteptwo(@Param("deptId")Integer deptId);
<!-- List<Emp> getDeptAndEmpBySteptwo(@Param("deptId")Integer deptId);--><select id="getDeptAndEmpBySteptwo" resultType="Emp">select * from t_emp where dept_id=#{dept_id}</select>
<resultMap id="deptAndEmpResultMapByStep" type="Dept"><id column="dept_id" property="deptId"></id><result column="dept_name" property="deptName"></result><collection property="emps" fetchType="eager"select="com.fjp.mybatis.mapper.EmpMapper.getDeptAndEmpBySteptwo"column="dept_id"></collection>
</resultMap>
<select id="getDeptAndEmpByStepOne" resultMap="deptAndEmpResultMapByStep">select * from t_dept where dept_id=#{deptId}
</select>
public void testDeptAndEmpByDept(){SqlSession sqlSession = SqlSessionUtil.getSqlSession();DeptMapper mapper= sqlSession.getMapper(DeptMapper.class);Dept dept = mapper.getDeptAndEmpByStepOne(1);System.out.println(dept);
}
1.8 动态SQL
是一种根据特定条件动态拼接SQL语句的功能,它存在的意义是为了解决拼接sql语句字符串时的痛点问题
1.if
<!--if:通过test属性中的表达式判断标签中的内容是否有效(是否会拼接到sql中)
由于if中有一个不满足时会报错,可以在where后面加一个恒成立的条件如1=1
-->
<!-- List<Emp> getEmpByCondition(Emp emp);--><select id="getEmpByCondition" resultType="Emp">select * from t_emp where<if test="empName !=null and empName !=''">emp_name=#{empName}</if><if test="age !=null and age !=''">and age=#{age}</if><if test="gender !=null and gender !=''">and gender=#{gender}</if></select>
/*** 根据条件查询员工信息* @param emp* @return*/
List<Emp> getEmpByCondition(Emp emp);
2.where
where:标签中成立自动生成where,
一个条件不成立时,可以将多余的and去掉
当所有标签都不成立时,查询所有结果(等于自动补充一个恒成立的条件)
--><!-- List<Emp> getEmpByCondition(Emp emp);--><select id="getEmpByCondition" resultType="Emp">select * from t_emp<where><if test="empName !=null and empName !=''">emp_name=#{empName}</if><if test="age !=null and age !=''">and age=#{age}</if><if test="gender !=null and gender !=''">and gender=#{gender}</if></where></select>
3.trim
<!--trim可以在指定内容内添加指定标签,也可以去除指定标签prefix:在内容前添加指定标签prefixOverrides:去除内容前的指定标签suffix:在指定内容后添加指定标签prefixOverrides:去除内容后的指定标签
-->
<select id="getEmpByCondition" resultType="Emp">select * from t_emp<trim prefix="where" suffixOverrides="and"><if test="empName !=null and empName !=''">emp_name=#{empName} and</if><if test="age !=null and age !=''">age=#{age} and</if><if test="gender !=null and gender !=''">gender=#{gender}</if></trim></select>
4.choose、when、otherwise
类似于 if…else if…else
<!-- List<Emp> getEmpByChoose(Emp emp);
类似于 if...else if...else
其中when最少设置一个,otherwise最多设置一个
--><select id="getEmpByChoose" resultType="Emp">select * from t_emp<where><choose><when test="empName!=null and empName != ''">emp_name=#{empName}</when><when test="age != null and age!=''">age=#{age}</when><when test="gender != null and gender !=''">gender=#{gender}</when></choose></where></select>
5.foreach
批量添加
/*** 批量添加员工信息* @param emps*/
void insertMoreEmp(@Param("emps") List<Emp> emps);
<insert id="insertMoreEmp">insert into t_emp values<foreach collection="emps" item="emp" separator=",">(null, #{emp.empName}, #{emp.age}, #{emp.gender}, null)</foreach>
</insert>
@Test
public void testInsertMoreEmp(){SqlSession sqlSession = SqlSessionUtil.getSqlSession();DynamicSQLMapper mapper = sqlSession.getMapper(DynamicSQLMapper.class);Emp emp = new Emp(null, "小花", 18, "男");Emp emp1 = new Emp(null, "小明", 18, "男");Emp emp2 = new Emp(null, "小李", 18, "男");Emp emp3 = new Emp(null, "小猪", 18, "男");List<Emp> emps = Arrays.asList(emp, emp1, emp2, emp3);mapper.insertMoreEmp(emps);sqlSession.commit();
}
批量删除
/*** 批量删除* @param empIds*/
void deleteMoveEmp(@Param("empIds") Integer[] empIds);
<!-- void deleteMoveEmp(@Param("empIds") Integer[] empIds);--><delete id="deleteMoveEmp">delete from t_emp where emp_id in(<foreach collection="empIds" item="empId" separator=",">#{empId}</foreach>)</delete>
@Test
public void testDeleteMoveEmp(){SqlSession sqlSession = SqlSessionUtil.getSqlSession();DynamicSQLMapper mapper = sqlSession.getMapper(DynamicSQLMapper.class);Integer[] empIds=new Integer[]{6,7};mapper.deleteMoveEmp(empIds);
}
delete from t_emp where
<foreach collection="empIds" item="empId" separator="or">emp_id=#{empId}
</foreach>
<sql id="empColumns">emp_id,emp_name,age,gender</sql>
<!-- 使用include引入-->
1.9 mybatis的缓存(对于查询功能有效)
1.mybatis的一级缓存(默认开启)
一级缓存是SqlSession级别的,通过一个SqlSession查询数据会被缓存,下次查询相同的数据,就会从缓存中直接获取,不会从数据库重新访问
使一级缓存失效的四种情况:
- 不同的SqlSession对应不同的一级缓存
- 同一个SqlSession但是查询条件不同
- 同一个SqlSession两次查询期间执行了任何一次增删查改操作
- 同一个SqlSession两次查询期间手动清空了缓存(clearCatch)
2.mybatis的二级缓存
二级缓存是SqlSessionFactory级别,通过同一个SqlSessionFactory创建的SqlSession查询结果会被缓存,此后若再次执行相同的查询语句,结果就会从缓存中获取。
二级缓存开启的条件:
- 在核心配置文件中,设置全局配置属性cacheEnabled=“true”,默认为true,不需要设置
- 在映射文件中设置标签
- 二级缓存必须在sqlsession关闭或提交之后有效‘
- 查询的数据所转换的实体类类型必须实现序列化接口
使二级缓存失效的情况:
两次查询之间任意的增删改,会使一级缓存和二级缓存同时失效
3.二级缓存的相关配置
在mapper配置文件中添加cache标签可以设置一些属性
-
eviction属性:缓存回收策略,默认是LRU
LRU:最近最少使用的,移除最长时间不被使用的对象
FIFO:先进先出,按对象进入缓存的顺序来移除tamen
SOFT:软引用,移除基于垃圾回收器状态和软引用规则的对象
WEAK:弱引用,更积极的移除基于垃圾回收器状态和弱引用规则的对象
-
flushInterval属性:刷新间隔,单位毫秒
默认情况是不设置,也就是没有刷新间隔,缓存仅仅调用语句时刷新
-
size属性,引用数目,正整数
代表缓存最多可以存储多少个对象,太大容易导致内存溢出
-
readOnly属性:只读,true/false
true:只读缓存,会给所有调用者返回缓存对象的相同实例,因此这些对象不能被修改,这提供了很重要的性能优势
false:读写缓存,会返回缓存对象的拷贝(通过序列化)这会慢一些,但是安全,因此会默认是false
4.mybatis缓存查询的顺序
先查询二级缓存,因为二级缓存中可能会有其他程序已经查出来的数据,可以拿来直接使用
如果二级缓存没有命中,再查询一级缓存
如果一级缓存也没有命中,则查询数据库
SqlSession关闭之后,一级缓存中的数据会写入二级缓存
5.整合第三方缓存EHCache
1.pom.xml导入依赖
<!-- Mybatis EHCache整合包 -->
<dependency><groupId>org.mybatis.caches</groupId><artifactId>mybatis-ehcache</artifactId><version>1.2.1</version>
</dependency>
<!-- slf4j日志门面的一个具体实现 -->
<dependency><groupId>ch.qos.logback</groupId><artifactId>logback-classic</artifactId><version>1.2.3</version>
</dependency>
2.创建EHCache的配置文件ehcache.xml
<?xml version="1.0" encoding="utf-8" ?>
<ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="../config/ehcache.xsd">
<!-- 磁盘保存路径 -->
<diskStore path="D:\后端学习\资料\SSM"/>
<defaultCachemaxElementsInMemory="1000"maxElementsOnDisk="10000000"eternal="false"overflowToDisk="true"timeToIdleSeconds="120"timeToLiveSeconds="120"diskExpiryThreadIntervalSeconds="120"memoryStoreEvictionPolicy="LRU">
</defaultCache>
</ehcache>
3.设置二级缓存类型
在mapper配置文件中设置
<cache type="org.mybatis.caches.ehcache.EhcacheCache"/>
4.加入logback日志
<?xml version="1.0" encoding="UTF-8"?>
<configuration debug="true">
<!-- 指定日志输出的位置 -->
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<!-- 日志输出的格式 -->
<!-- 按照顺序分别是: 时间、日志级别、线程名称、打印日志的类、日志主体内容、换行
-->
<pattern>[%d{HH:mm:ss.SSS}] [%-5level] [%thread] [%logger]
[%msg]%n</pattern>
</encoder>
</appender>
<!-- 设置全局日志级别。日志级别按顺序分别是: DEBUG、INFO、WARN、ERROR -->
<!-- 指定任何一个日志级别都只打印当前级别和后面级别的日志。 -->
<root level="DEBUG">
<!-- 指定打印日志的appender,这里通过“STDOUT”引用了前面配置的appender -->
<appender-ref ref="STDOUT" />
</root>
<!-- 根据特殊需求指定局部日志级别 -->
<logger name="com.fjp.mybatis.mapper" level="DEBUG"/>
</configuration>
1.10 mybatis的逆向工程
正向工程:先创建java实体类,由框架负责根据实体类生成数据库表,Hibernate是支持正向工程
逆向工程:先创建数据库表,由框架负责根据数据库表,反向生成如下资源
java实体类
Mapper接口
Mapper映射文件
1.创建逆向工程的步骤
1.添加插件和依赖
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"><modelVersion>4.0.0</modelVersion><groupId>com.fjp.mybatis</groupId><artifactId>mybatis_mbg</artifactId><version>1.0-SNAPSHOT</version><packaging>jar</packaging>
<dependencies><dependency><groupId>org.mybatis</groupId><artifactId>mybatis</artifactId><version>3.5.10</version></dependency><!-- junit测试 --><dependency><groupId>junit</groupId><artifactId>junit</artifactId><version>4.12</version><scope>test</scope></dependency><!-- log4j日志 --><dependency><groupId>log4j</groupId><artifactId>log4j</artifactId><version>1.2.12</version></dependency><dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId><version>5.1.6</version></dependency>
</dependencies><!-- 控制Maven在构建过程中相关配置 -->
<build><!-- 构建过程中用到的插件 --><plugins><!-- 具体插件,逆向工程的操作是以构建过程中插件形式出现的 --><plugin><groupId>org.mybatis.generator</groupId><artifactId>mybatis-generator-maven-plugin</artifactId><version>1.3.0</version><!-- 插件的依赖 -->
<dependencies><!-- 逆向工程的核心依赖 --><dependency><groupId>org.mybatis.generator</groupId><artifactId>mybatis-generator-core</artifactId><version>1.3.2</version></dependency><!-- MySQL驱动 --><dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId><version>8.0.16</version></dependency>
</dependencies>
</plugin>
</plugins>
</build>
</project>
2.创建mybatis的核心配置文件
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configurationPUBLIC "-//mybatis.org//DTD Config 3.0//EN""http://mybatis.org/dtd/mybatis-3-config.dtd"><configuration><properties resource="mappers/jdbc.properties"/><settings><!--将下划线映射为驼峰--><setting name="mapUnderscoreToCamelCase" value="true"/></settings><typeAliases><package name=""/></typeAliases><environments default="development"><environment id="development"><transactionManager type="JDBC"/><dataSource type="POOLED"><property name="driver" value="${jdbc.driver}"/><property name="url" value="${jdbc.url}"/><property name="username" value="${jdbc.root}"/><property name="password" value="${jdbc.password}"/></dataSource></environment></environments><mappers><package name=""/></mappers>
</configuration>
3.逆向工程的核心配置文件
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE generatorConfiguration
PUBLIC "-//mybatis.org//DTD MyBatis Generator Configuration 1.0//EN"
"http://mybatis.org/dtd/mybatis-generator-config_1_0.dtd">
<generatorConfiguration>
<!--
targetRuntime: 执行生成的逆向工程的版本
MyBatis3Simple: 生成基本的CRUD(清新简洁版)
MyBatis3: 生成带条件的CRUD(奢华尊享版)
-->
<context id="DB2Tables" targetRuntime="MyBatis3">
<!-- 数据库的连接信息 -->
<jdbcConnection driverClass="com.mysql.cj.jdbc.Driver"
connectionURL="jdbc:mysql://localhost:3306/mybatis?
serverTimezone=UTC"
userId="root"
password="123456">
</jdbcConnection>
<!-- javaBean的生成策略-->
<javaModelGenerator targetPackage="com.atguigu.mybatis.pojo"
targetProject=".\src\main\java">
<property name="enableSubPackages" value="true" />
<property name="trimStrings" value="true" />
</javaModelGenerator>
<!-- SQL映射文件的生成策略 -->
<sqlMapGenerator targetPackage="com.atguigu.mybatis.mapper"
targetProject=".\src\main\resources">
<property name="enableSubPackages" value="true" />
</sqlMapGenerator>
<!-- Mapper接口的生成策略 -->
<javaClientGenerator type="XMLMAPPER"
targetPackage="com.atguigu.mybatis.mapper" targetProject=".\src\main\java">
<property name="enableSubPackages" value="true" />
</javaClientGenerator>
<!-- 逆向分析的表 -->
<!-- tableName设置为*号,可以对应所有表,此时不写domainObjectName -->
<!-- domainObjectName属性指定生成出来的实体类的类名 -->
<table tableName="t_emp" domainObjectName="Emp"/>
<table tableName="t_dept" domainObjectName="Dept"/>
</context>
</generatorConfiguration>
@Testpublic void testMBG(){SqlSession sqlSession = SqlSessionUtil.getSqlSession();EmpMapper mapper = sqlSession.getMapper(EmpMapper.class);//根据id查询数据
// Emp emp = mapper.selectByPrimaryKey(1);
// System.out.println(emp);//查询所有数据
// List<Emp> emps = mapper.selectByExample(null);
// System.out.println(emps);//根据条件查询
// EmpExample empExample = new EmpExample();
// empExample.createCriteria().andEmpNameEqualTo("张三").andAgeGreaterThanOrEqualTo(18);
// empExample.or().andGenderEqualTo("女");
// List<Emp> emps = mapper.selectByExample(empExample);
// System.out.println(emps);//测试修改功能Emp emp = new Emp(1,"liyit",null,"男",1);
// int i = mapper.updateByPrimaryKey(emp);//选择性修改mapper.updateByPrimaryKeySelective(emp);
1.11分页插件
limit index,pageSize
index:当前页的起始索引=(pageNo-1)x pageSize
pageSize=4 pageNo=1 index=0 limit 0,4
pageSize=4 pageNo=3 index=8 limit 8,4
pageSize=4 pageNo index=20 limit 20,4
pagesize:每页显示的条数
pageNo:当前页的页码
count:总记录数
totalPage:总页数
totalPage = count / pageSize;if(count % pageSize != 0){ totalPage += 1;}
首页 上一页 2 3 4 5 6 下一页 末页
1.分页插件的使用步骤
添加依赖
<dependency>
<groupId>com.github.pagehelper</groupId>
<artifactId>pagehelper</artifactId>
<version>5.2.0</version>
</dependency>
配置分页插件
在mybatis核心配置中
<plugins><plugin interceptor="com.github.pagehelper.PageInterceptor"></plugin>
</plugins>
分页插件的使用
a>在查询功能之前使用PageHelper.startPage(int pageNum, int pageSize)开启分页功能
pageNum:当前页的页码
pageSize:每页显示的条数 b>在查询获取list集合之后,使用PageInfo pageInfo = new PageInfo<>(List list, int navigatePages)获取分页相关数据
list:分页之后的数据
navigatePages:导航分页的页码数 c>
分页相关数据 PageInfo{ pageNum=8, pageSize=4, size=2, startRow=29, endRow=30, total=30, pages=8, list=Page{count=true, pageNum=8, pageSize=4, startRow=28, endRow=32, total=30, pages=8, reasonable=false, pageSizeZero=false}, prePage=7, nextPage=0, isFirstPage=false, isLastPage=true, hasPreviousPage=true, hasNextPage=false, navigatePages=5, navigateFirstPage4, navigateLastPage8, navigatepageNums=[4, 5, 6, 7, 8] }
pageNum:当前页的页码
pageSize:每页显示的条数
size:当前页显示的真实条数
total:总记录数
pages:总页数
prePage:上一页的页码
nextPage:下一页的页码
isFirstPage/isLastPage:是否为第一页/最后一页
hasPreviousPage/hasNextPage:是否存在上一页/下一页
navigatePages:导航分页的页码数
navigatepageNums:导航分页的页码,[1,2,3,4,5]
三、Spring MVC
spring mvc模式将应用程序划分为视图(view)、模型(model)、控制器(controller)
1.第一个SpringMVC程序
第一步导入pom依赖
<dependencies><dependency><groupId>org.springframework</groupId><artifactId>spring-context</artifactId><version>5.2.15.RELEASE</version></dependency><dependency><groupId>org.springframework</groupId><artifactId>spring-web</artifactId><version>5.2.15.RELEASE</version></dependency><dependency><groupId>org.springframework</groupId><artifactId>spring-webmvc</artifactId><version>5.2.15.RELEASE</version></dependency><dependency><groupId>javax.servlet.jsp</groupId><artifactId>jsp-api</artifactId><version>2.0</version></dependency><dependency><groupId>javax.servlet</groupId><artifactId>javax.servlet-api</artifactId><version>3.1.0</version></dependency><dependency><groupId>jstl</groupId><artifactId>jstl</artifactId><version>1.2</version></dependency><dependency><groupId>javax.servlet.jsp</groupId><artifactId>jsp-api</artifactId><version>2.0</version></dependency>
</dependencies><build><resources><resource><directory>src/main/java</directory><includes><include>**/*.properties</include><include>**/*.xml</include></includes><filtering>false</filtering></resource><resource><directory>src/main/resources</directory><includes><include>**/*.properties</include><include>**/*.xml</include></includes><filtering>false</filtering></resource></resources>
</build>
第二步、配置相关web.xml文件
<!-- 注册servlset--><!--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>
第三步、配置springmvc.xml
<!-- 自动扫描包,让指定包下的注解生效,由IOC容器统一管理 -->
<context:component-scan base-package="com.fjp.controller"></context:component-scan>
<!-- 让Spring MVC不处理静态资源 -->
<mvc:default-servlet-handler />
<!--支持mvc注解驱动在spring中一般采用@RequestMapping注解来完成映射关系要想使@RequestMapping注解生效必须向上下文中注册DefaultAnnotationHandlerMapping和一个AnnotationMethodHandlerAdapter实例这两个实例分别在类级别和方法级别处理。而annotation-driven配置帮助我们自动完成上述两个实例的注入。-->
<mvc:annotation-driven /><!--视图解析器:DispatcherServlet给他的ModelAndView-->
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver" id="InternalResourceViewResolver"><!--前缀--><property name="prefix" value="/WEB-INF/view/"/><!--后缀--><property name="suffix" value=".jsp"/>
</bean>
第四步、创建controller
controller层需要添加controller注解,相关的方法添加RequestMapping(“/访问地址”)
@Controller
public class UserController {@RequestMapping({"/hello"})public String hello(Model model){model.addAttribute("meg","hello啊,树哥!");return "hello";}}
第五步、创建jsp页面
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head><title>Title</title>
</head>
<body>
<h1>${meg}</h1>
<h2>${meg}</h2>
<h3>${meg}</h3>
<h4>${meg}</h4>
<h5>${meg}</h5>
<h6>${meg}</h6>
</body>
</html>
2.Controller配置总结
1.Controller控制器
- 控制器复杂提供访问应用程序的行为,通常通过接口定义或注解定义两种方法实现。
- 控制器负责解析用户的请求并将其转换为一个模型。
- 在Spring MVC中一个控制器类可以包含多个方法
- 在Spring MVC中,对于Controller的配置方式有很多种
2.注解和接口实现Controller
-
实现接口Controller定义控制器是较老的办法
-
缺点是:一个控制器中只有一个方法,如果要多个方法则需要定义多个Controller;定义的方式比较麻烦
-
@Controller注解类型用于声明Spring类的实例是一个控制器(在讲IOC时还提到了另外3个注解);
-
Spring可以使用扫描机制来找到应用程序中所有基于注解的控制器类,为了保证Spring能找到你的控制器,需要在配置文件中声明组件扫描。
//@Controller注解的类会自动添加到Spring上下文中
@Controller
public class ControllerTest2{//映射访问路径@RequestMapping("/t2")public String index(Model model){//Spring MVC会自动实例化一个Model对象用于向视图中传值model.addAttribute("msg", "ControllerTest2");//返回视图位置return "test";}}
3.RequestMapping
-
@RequestMapping注解用于映射url到控制器类或一个特定的处理程序方法。可用于类或方法上。用于类上,表示类中的所有响应请求的方法都是以该地址作为父路径。
-
为了测试结论更加准确,我们可以加上一个项目名测试 myweb
-
只注解在方法上面
@Controller public class TestController {@RequestMapping("/h1")public String test(){return "test";} }
访问路径:http://localhost:8080 / 项目名 / h1
-
同时注解类与方法
@Controller @RequestMapping("/admin") public class TestController {@RequestMapping("/h1")public String test(){return "test";} }
访问路径:http://localhost:8080 / 项目名/ admin /h1 , 需要先指定类的路径再指定方法的路径;
4.RestFul风格
概念:
RestFul是一个资源定位及资源操作的风格,不是标准也不是协议,只是一种风格,基于则合格风格设计的软件更加简介,更有层次,更易于实现缓存机制
功能:
资源:互联网所有的事物都可以被抽象为资源
资源操作:使用POST、DELETE、PUT、GET,使用不同方法对资源进行操作。
分别对应 添加、 删除、修改、查询。
传统方式操作资源:
通过不同的参数来实现不同的效果!方法单一,post 和 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
@Controller public class RestFulController { }
-
在Spring MVC中可以使用 @PathVariable 注解,让方法参数的值对应绑定到一个URI模板变量上。
@Controller public class RestFulController {//映射访问路径@RequestMapping("/commit/{p1}/{p2}")public String index(@PathVariable int p1, @PathVariable int p2, Model model){int result = p1+p2;//Spring MVC会自动实例化一个Model对象用于向视图中传值model.addAttribute("msg", "结果:"+result);//返回视图位置return "test";}}
使用method属性指定请求类型
用于约束请求的类型,可以收窄请求范围。指定请求谓词的类型如GET, POST, HEAD, OPTIONS, PUT, PATCH, DELETE, TRACE等
我们来测试一下:
-
增加一个方法
//映射访问路径,必须是POST请求 @RequestMapping(value = "/hello",method = {RequestMethod.POST}) public String index2(Model model){model.addAttribute("msg", "hello!");return "test"; }
小结:
Spring MVC 的 @RequestMapping 注解能够处理 HTTP 请求的方法, 比如 GET, PUT, POST, DELETE 以及 PATCH。
所有的地址栏请求默认都会是 HTTP GET 类型的。
方法级别的注解变体有如下几个:组合注解
@GetMapping
@PostMapping
@PutMapping
@DeleteMapping
@PatchMapping
@GetMapping 是一个组合注解,平时使用的会比较多!
它所扮演的是 @RequestMapping(method =RequestMethod.GET) 的一个快捷方式。
5.结果跳转方式
1.通过SpringMVC来实现转发和重定向 - 无需视图解析器;
测试前,需要将视图解析器注释掉
@Controller
public class ResultSpringMVC {@RequestMapping("/rsm/t1")public String test1(){//转发return "/index.jsp";}@RequestMapping("/rsm/t2")public String test2(){//转发二return "forward:/index.jsp";}@RequestMapping("/rsm/t3")public String test3(){//重定向return "redirect:/index.jsp";}
}
通过SpringMVC来实现转发和重定向 - 有视图解析器;
重定向 , 不需要视图解析器 , 本质就是重新请求一个新地方嘛 , 所以注意路径问题.
可以重定向到另外一个请求实现 .
@Controller
public class ResultSpringMVC2 {@RequestMapping("/rsm2/t1")public String test1(){//转发return "test";}@RequestMapping("/rsm2/t2")public String test2(){//重定向return "redirect:/index.jsp";//return "redirect:hello.do"; //hello.do为另一个请求/}}
6.数据处理
处理提交数据
1、提交的域名称和处理方法的参数名一致
提交数据 : http://localhost:8080/hello?name=kuangshen
处理方法 :
@RequestMapping("/hello")
public String hello(String name){System.out.println(name);return "hello";
}
后台输出 : kuangshen
2、提交的域名称和处理方法的参数名不一致
提交数据 : http://localhost:8080/hello?username=kuangshen
处理方法 :
//@RequestParam("username") : username提交的域的名称 .
@RequestMapping("/hello")
public String hello(@RequestParam("username") String name){System.out.println(name);return "hello";
}
后台输出 : kuangshen
3、提交的是一个对象
要求提交的表单域和对象的属性名一致 , 参数使用对象即可
1、实体类
public class User {private int id;private String name;private int age;//构造//get/set//tostring()
}
2、提交数据 : http://localhost:8080/mvc04/user?name=kuangshen&id=1&age=15
3、处理方法 :
@RequestMapping("/user")
public String user(User user){System.out.println(user);return "hello";
}
后台输出 : User { id=1, name=‘kuangshen’, age=15 }
说明:如果使用对象的话,前端传递的参数名和对象名必须一致,否则就是null。
数据显示到前端
第一种 : 通过ModelAndView
我们前面一直都是如此 . 就不过多解释
public class ControllerTest1 implements Controller {public ModelAndView handleRequest(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse) throws Exception {//返回一个模型视图对象ModelAndView mv = new ModelAndView();mv.addObject("msg","ControllerTest1");mv.setViewName("test");return mv;}
}
第二种 : 通过ModelMap
ModelMap
@RequestMapping("/hello")
public String hello(@RequestParam("username") String name, ModelMap model){//封装要显示到视图中的数据//相当于req.setAttribute("name",name);model.addAttribute("name",name);System.out.println(name);return "hello";
}
第三种 : 通过Model
Model
@RequestMapping("/ct2/hello")
public String hello(@RequestParam("username") String name, Model model){//封装要显示到视图中的数据//相当于req.setAttribute("name",name);model.addAttribute("msg",name);System.out.println(name);return "test";
}
对比
就对于新手而言简单来说使用区别就是:
Model 只有寥寥几个方法只适合用于储存数据,简化了新手对于Model对象的操作和理解;ModelMap 继承了 LinkedMap ,除了实现了自身的一些方法,同样的继承 LinkedMap 的方法和特性;ModelAndView 可以在储存数据的同时,可以进行设置返回的逻辑视图,进行控制展示层的跳转。
当然更多的以后开发考虑的更多的是性能和优化,就不能单单仅限于此的了解。
7.乱码
不得不说,乱码问题是在我们开发中十分常见的问题,也是让我们程序猿比较头大的问题!
以前乱码问题通过过滤器解决 , 而SpringMVC给我们提供了一个过滤器 , 可以在web.xml中配置 .
修改了xml文件需要重启服务器!
<filter><filter-name>encoding</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>encoding</filter-name><url-pattern>/*</url-pattern>
</filter-mapping>
但是我们发现 , 有些极端情况下.这个过滤器对get的支持不好 .
处理方法 :
1、修改tomcat配置文件 :设置编码!
<Connector URIEncoding="utf-8" port="8080" protocol="HTTP/1.1"connectionTimeout="20000"redirectPort="8443" />
2、自定义过滤器
package com.kuang.filter;import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.util.Map;/**
* 解决get和post请求 全部乱码的过滤器
*/
public class GenericEncodingFilter implements Filter {@Overridepublic void destroy() {}@Overridepublic void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {//处理response的字符编码HttpServletResponse myResponse=(HttpServletResponse) response;myResponse.setContentType("text/html;charset=UTF-8");// 转型为与协议相关对象HttpServletRequest httpServletRequest = (HttpServletRequest) request;// 对request包装增强HttpServletRequest myrequest = new MyRequest(httpServletRequest);chain.doFilter(myrequest, response);}@Overridepublic void init(FilterConfig filterConfig) throws ServletException {}}//自定义request对象,HttpServletRequest的包装类
class MyRequest extends HttpServletRequestWrapper {private HttpServletRequest request;//是否编码的标记private boolean hasEncode;//定义一个可以传入HttpServletRequest对象的构造函数,以便对其进行装饰public MyRequest(HttpServletRequest request) {super(request);// super必须写this.request = request;}// 对需要增强方法 进行覆盖@Overridepublic Map getParameterMap() {// 先获得请求方式String method = request.getMethod();if (method.equalsIgnoreCase("post")) {// post请求try {// 处理post乱码request.setCharacterEncoding("utf-8");return request.getParameterMap();} catch (UnsupportedEncodingException e) {e.printStackTrace();}} else if (method.equalsIgnoreCase("get")) {// get请求Map<String, String[]> parameterMap = request.getParameterMap();if (!hasEncode) { // 确保get手动编码逻辑只运行一次for (String parameterName : parameterMap.keySet()) {String[] values = parameterMap.get(parameterName);if (values != null) {for (int i = 0; i < values.length; i++) {try {// 处理get乱码values[i] = new String(values[i].getBytes("ISO-8859-1"), "utf-8");} catch (UnsupportedEncodingException e) {e.printStackTrace();}}}}hasEncode = true;}return parameterMap;}return super.getParameterMap();}//取一个值@Overridepublic String getParameter(String name) {Map<String, String[]> parameterMap = getParameterMap();String[] values = parameterMap.get(name);if (values == null) {return null;}return values[0]; // 取回参数的第一个值}//取所有值@Overridepublic String[] getParameterValues(String name) {Map<String, String[]> parameterMap = getParameterMap();String[] values = parameterMap.get(name);return values;}
}
这个也是我在网上找的一些大神写的,一般情况下,SpringMVC默认的乱码处理就已经能够很好的解决了!
然后在web.xml中配置这个过滤器即可!
乱码问题,需要平时多注意,在尽可能能设置编码的地方,都设置为统一编码 UTF-8!
四、JSON
1.什么是json
- JSON(JavaScript Object Notation, JS 对象标记) 是一种轻量级的数据交换格式,目前使用特别广泛。
- 采用完全独立于编程语言的文本格式来存储和表示数据。
- 简洁和清晰的层次结构使得 JSON 成为理想的数据交换语言。
- 易于人阅读和编写,同时也易于机器解析和生成,并有效地提升网络传输效率。
在 JavaScript 语言中,一切都是对象。因此,任何JavaScript 支持的类型都可以通过 JSON 来表示,例如字符串、数字、对象、数组等。看看他的要求和语法格式:
- 对象表示为键值对,数据由逗号分隔
- 花括号保存对象
- 方括号保存数组
JSON 键值对是用来保存 JavaScript 对象的一种方式,和 JavaScript 对象的写法也大同小异,键/值对组合中的键名写在前面并用双引号 “” 包裹,使用冒号 : 分隔,然后紧接着值:
{"name": "QinJiang"}
{"age": "3"}
{"sex": "男"}
很多人搞不清楚 JSON 和 JavaScript 对象的关系,甚至连谁是谁都不清楚。其实,可以这么理解:
JSON 是 JavaScript 对象的字符串表示法,它使用文本表示一个 JS 对象的信息,本质是一个字符串。
var obj = {a: 'Hello', b: 'World'}; //这是一个对象,注意键名也是可以使用引号包裹的
var json = '{"a": "Hello", "b": "World"}'; //这是一个 JSON 字符串,本质是一个字符串
JSON 和 JavaScript 对象互转
要实现从JSON字符串转换为JavaScript 对象,使用 JSON.parse() 方法:
var obj = JSON.parse('{"a": "Hello", "b": "World"}');
//结果是 {a: 'Hello', b: 'World'}
要实现从JavaScript 对象转换为JSON字符串,使用 JSON.stringify() 方法:
var json = JSON.stringify({a: 'Hello', b: 'World'});
//结果是 '{"a": "Hello", "b": "World"}'
代码测试
1、新建一个module ,springmvc-05-json , 添加web的支持
2、在web目录下新建一个 json-1.html , 编写测试内容
<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><title>JSON_秦疆</title>
</head>
<body><script type="text/javascript">//编写一个js的对象var user = {name:"秦疆",age:3,sex:"男"};//将js对象转换成json字符串var str = JSON.stringify(user);console.log(str);//将json字符串转换为js对象var user2 = JSON.parse(str);console.log(user2.age,user2.name,user2.sex);</script></body>
</html>
Jackson应该是目前比较好的json解析工具了
当然工具不止这一个,比如还有阿里巴巴的 fastjson 等等。
我们这里使用Jackson,使用它需要导入它的jar包;
<!-- https://mvnrepository.com/artifact/com.fasterxml.jackson.core/jackson-core -->
<dependency><groupId>com.fasterxml.jackson.core</groupId><artifactId>jackson-databind</artifactId><version>2.9.8</version>
</dependency>
配置SpringMVC需要的配置
乱码统一解决
上一种方法比较麻烦,如果项目中有许多请求则每一个都要添加,可以通过Spring配置统一指定,这样就不用每次都去处理了!
我们可以在springmvc的配置文件上添加一段消息StringHttpMessageConverter转换配置!
<mvc:annotation-driven><mvc:message-converters register-defaults="true"><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>
返回json字符串统一解决
在类上直接使用 @RestController ,这样子,里面所有的方法都只会返回 json 字符串了,不用再每一个都添加@ResponseBody !我们在前后端分离开发中,一般都使用 @RestController ,十分便捷!
@RestController
public class UserController {//produces:指定响应体返回类型和编码@RequestMapping(value = "/json1")public String json1() throws JsonProcessingException {//创建一个jackson的对象映射器,用来解析数据ObjectMapper mapper = new ObjectMapper();//创建一个对象User user = new User("秦疆1号", 3, "男");//将我们的对象解析成为json格式String str = mapper.writeValueAsString(user);//由于@ResponseBody注解,这里会将str转成json格式返回;十分方便return str;}}
五、SSM整合
parameterName);
if (values != null) {
for (int i = 0; i < values.length; i++) {
try {
// 处理get乱码
values[i] = new String(values[i]
.getBytes(“ISO-8859-1”), “utf-8”);
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
}
}
}
hasEncode = true;
}
return parameterMap;
}
return super.getParameterMap();
}
//取一个值
@Override
public String getParameter(String name) {
Map<String, String[]> parameterMap = getParameterMap();
String[] values = parameterMap.get(name);
if (values == null) {
return null;
}
return values[0]; // 取回参数的第一个值
}
//取所有值
@Override
public String[] getParameterValues(String name) {
Map<String, String[]> parameterMap = getParameterMap();
String[] values = parameterMap.get(name);
return values;
}
}
这个也是我在网上找的一些大神写的,一般情况下,SpringMVC默认的乱码处理就已经能够很好的解决了!**然后在web.xml中配置这个过滤器即可!**乱码问题,需要平时多注意,在尽可能能设置编码的地方,都设置为统一编码 UTF-8!# 四、JSON## 1.什么是json- JSON(JavaScript Object Notation, JS 对象标记) 是一种轻量级的数据交换格式,目前使用特别广泛。
- 采用完全独立于编程语言的**文本格式**来存储和表示数据。
- 简洁和清晰的层次结构使得 JSON 成为理想的数据交换语言。
- 易于人阅读和编写,同时也易于机器解析和生成,并有效地提升网络传输效率。在 JavaScript 语言中,一切都是对象。因此,任何JavaScript 支持的类型都可以通过 JSON 来表示,例如字符串、数字、对象、数组等。看看他的要求和语法格式:- 对象表示为键值对,数据由逗号分隔
- 花括号保存对象
- 方括号保存数组**JSON 键值对**是用来保存 JavaScript 对象的一种方式,和 JavaScript 对象的写法也大同小异,键/值对组合中的键名写在前面并用双引号 "" 包裹,使用冒号 : 分隔,然后紧接着值:```json
{"name": "QinJiang"}
{"age": "3"}
{"sex": "男"}
很多人搞不清楚 JSON 和 JavaScript 对象的关系,甚至连谁是谁都不清楚。其实,可以这么理解:
JSON 是 JavaScript 对象的字符串表示法,它使用文本表示一个 JS 对象的信息,本质是一个字符串。
var obj = {a: 'Hello', b: 'World'}; //这是一个对象,注意键名也是可以使用引号包裹的
var json = '{"a": "Hello", "b": "World"}'; //这是一个 JSON 字符串,本质是一个字符串
JSON 和 JavaScript 对象互转
要实现从JSON字符串转换为JavaScript 对象,使用 JSON.parse() 方法:
var obj = JSON.parse('{"a": "Hello", "b": "World"}');
//结果是 {a: 'Hello', b: 'World'}
要实现从JavaScript 对象转换为JSON字符串,使用 JSON.stringify() 方法:
var json = JSON.stringify({a: 'Hello', b: 'World'});
//结果是 '{"a": "Hello", "b": "World"}'
代码测试
1、新建一个module ,springmvc-05-json , 添加web的支持
2、在web目录下新建一个 json-1.html , 编写测试内容
<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><title>JSON_秦疆</title>
</head>
<body><script type="text/javascript">//编写一个js的对象var user = {name:"秦疆",age:3,sex:"男"};//将js对象转换成json字符串var str = JSON.stringify(user);console.log(str);//将json字符串转换为js对象var user2 = JSON.parse(str);console.log(user2.age,user2.name,user2.sex);</script></body>
</html>
Jackson应该是目前比较好的json解析工具了
当然工具不止这一个,比如还有阿里巴巴的 fastjson 等等。
我们这里使用Jackson,使用它需要导入它的jar包;
<!-- https://mvnrepository.com/artifact/com.fasterxml.jackson.core/jackson-core -->
<dependency><groupId>com.fasterxml.jackson.core</groupId><artifactId>jackson-databind</artifactId><version>2.9.8</version>
</dependency>
配置SpringMVC需要的配置
乱码统一解决
上一种方法比较麻烦,如果项目中有许多请求则每一个都要添加,可以通过Spring配置统一指定,这样就不用每次都去处理了!
我们可以在springmvc的配置文件上添加一段消息StringHttpMessageConverter转换配置!
<mvc:annotation-driven><mvc:message-converters register-defaults="true"><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>
返回json字符串统一解决
在类上直接使用 @RestController ,这样子,里面所有的方法都只会返回 json 字符串了,不用再每一个都添加@ResponseBody !我们在前后端分离开发中,一般都使用 @RestController ,十分便捷!
@RestController
public class UserController {//produces:指定响应体返回类型和编码@RequestMapping(value = "/json1")public String json1() throws JsonProcessingException {//创建一个jackson的对象映射器,用来解析数据ObjectMapper mapper = new ObjectMapper();//创建一个对象User user = new User("秦疆1号", 3, "男");//将我们的对象解析成为json格式String str = mapper.writeValueAsString(user);//由于@ResponseBody注解,这里会将str转成json格式返回;十分方便return str;}}