SSM+SpringBoot简单笔记,Typora导入

Spring

**IOC **(Inverse Of Control:反转控制)

**AOP **(Aspect Oriented Programming:面向切面编程)

1.Spring的体系结构:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-GRFuVdxC-1625401967485)(C:\Users\PMY\AppData\Roaming\Typora\typora-user-images\image-20210427083635106.png)]

2. Spring程序的开发步骤:

①导入 Spring 开发的基本包坐标

<dependency>
   <groupId>org.springframework</groupId>
   <artifactId>spring-context</artifactId>
   <version>5.0.5.RELEASE</version>
</dependency>

②编写 Dao 接口和实现类

③创建 Spring 核心配置文件

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

④在 Spring 配置文件中配置 UserDaoImpl

⑤使用 Spring 的 API 获得 Bean 实例

3.Spring的配置文件

3.1 Spring标签配置范围:

singleton 默认,单例

prototype 多例

request web项目中,Spring创建一个Bean的对象,将对象存入到 request 域中

session WEB 项目中,Spring 创建一个 Bean 的对象,将对象存入到 session 域中

global session WEB 项目中,应用在 Portlet 环境,如果没有 Portlet 环境那么global session相当于session

3.2 Bean生命周期配置

init-method:指定类中的初始化方法名称

destroy-method:指定类中销毁方法名称

3.3 Bean实例化三种方式

1) 使用无参构造方法实例化

​ 它会根据默认无参构造方法来创建类对象,如果bean中没有默认无参构造函数,将会创建失败

<bean id="userDao" class="com.itheima.dao.impl.UserDaoImpl"/>

2) 工厂静态方法实例化

​ 工厂的静态方法返回Bean实例

public class StaticFactoryBean {
    public static UserDao createUserDao(){    
    return new UserDaoImpl();
    }
}
<bean id="userDao" class="com.itheima.factory.StaticFactoryBean" 
      factory-method="createUserDao" />

3) 工厂实例方法实例化

​ 工厂的非静态方法返回Bean实例

public class DynamicFactoryBean {  
	public UserDao createUserDao(){        
		return new UserDaoImpl(); 
	}
}
<bean id="factoryBean" class="com.itheima.factory.DynamicFactoryBean"/>
<bean id="userDao" factory-bean="factoryBean" factory-method="createUserDao"/>

3.4 Bean的依赖注入

从 Spring 容器中获得 Bean进行操作

ApplicationContext applicationContext = new ClassPathXmlApplicationContext("Spring配置文件名");
applicationContext.getBean("userService");

参数的数据类型是字符串时,表示根据Bean的id从容器中获得Bean实例,返回是Object,需要强转。

参数的数据类型是Class类型时,表示根据类型从容器中匹配Bean实例,当容器中相同类型的Bean有多个时,则此方法会报错

3.5 Bean的依赖注入方式

①构造方法

<bean id="userDao" class="com.itheima.dao.impl.UserDaoImpl"/>
<bean id="userService" class="com.itheima.service.impl.UserServiceImpl">    <constructor-arg name="userDao" ref="userDao"></constructor-arg>
</bean>

②set方法

类中必须要有相应的set方法

配置Spring容器调用set方法进行注入

<bean id="userDao" class="com.itheima.dao.impl.UserDaoImpl"/>
<bean id="userService" class="com.itheima.service.impl.UserServiceImpl">
	<property name="userDao" ref="userDao"/>
</bean>

set方法:P命名空间注入

​ P命名空间注入本质也是set方法注入,但比起上述的set方法注入更加方便,主要体现在配置文件中,如下:

​ 首先,需要引入P命名空间:

xmlns:p="http://www.springframework.org/schema/p"

其次,需要修改注入方式

<bean id="userService" class="com.itheima.service.impl.UserServiceImpl" p:userDao-ref="userDao"/>

3.6 Bean的依赖注入的数据类型

注入数据的三种数据类型

  1. 普通数据类型

    value 属性

  2. 引用数据类型

    ref 属性

  3. 集合数据类型

    List类型:

     <list>
         <value>aaa</value>
         <value>bbb</value>
         <value>ccc</value>
     </list>
    

    List类型:

    <list>
     	<!--要么是bean的全限定名-->   
        <bean class="com.itheima.domain.User"/>
        <!--要么是已经配置好的对象引用-->
        <ref bean="u1"/>      
    </list>
    

    Map<String,User>类型:

    <map>            
        <entry key="user1" value-ref="u1"/>
        <entry key="user2" value-ref="u2"/>
    </map>
    

    集合数据类型 Properties

    <props>
        <prop key="p1">aaa</prop>
    </props>
    

3.7 引入其他配置文件(分模块开发)

<import resource="applicationContext-xxx.xml"/>

4. spring相关API

4.1 ApplicationContext的继承体系

applicationContext:接口类型,代表应用上下文,可以通过其实例获得 Spring 容器中的 Bean 对象

4.2 ApplicationContext的实现类

1)ClassPathXmlApplicationContext

​ 它是从类的根路径下加载配置文件 推荐使用这种

2)FileSystemXmlApplicationContext

​ 它是从磁盘路径上加载配置文件,配置文件可以在磁盘的任意位置。

3)AnnotationConfigApplicationContext

​ 当使用注解配置容器对象时,需要使用此类来创建 spring 容器。它用来读取注解。

5. Spring的注解开发

Spring容器加载properties文件

<context:property-placeholder location="xx.properties"/>
<property name="" value="${key}"/>

1.Spring原始注解

注解说明
@Component使用在类上用于实例化Bean
@Controller使用在web层类上用于实例化Bean
@Service使用在service层类上用于实例化Bean
@Repository使用在dao层类上用于实例化Bean
@Autowired使用在字段上用于根据类型依赖注入
@Qualifier结合@Autowired一起使用用于根据名称进行依赖注入
@Resource相当于@Autowired+@Qualifier,按照名称进行注入
@Value注入普通属性
@Scope标注Bean的作用范围
@PostConstruct使用在方法上标注该方法是Bean的初始化方法
@PreDestroy使用在方法上标注该方法是Bean的销毁方法

1、@Autowired 是通过 byType 的方式去注入的, 使用该注解,要求接口只能有一个实现类。
2、@Resource 可以通过 byName 和 byType的方式注入, 默认先按 byName的方式进行匹配,如果匹配不到,再按 byType的方式进行匹配。
3、@Qualifier 注解可以按名称注入, 但是注意是 类名

还需要用注解替代的配置:

非自定义的Bean的配置:
加载properties文件的配置:context:property-placeholder
组件扫描的配置:context:component-scan
引入其他文件:

新注解:

前三个是配置在配置类上面的

注解说明
@Configuration用于指定当前类是一个 Spring 配置类,当创建容器时会从该类上加载注解
@ComponentScan用于指定 Spring 在初始化容器时要扫描的包。 作用和在 Spring 的 xml 配置文件中的 <context:component-scan base-package=“com.itheima”/>一样
@Import用于导入其他配置类
@Bean使用在方法上,标注将该方法的返回值存储到 Spring 容器中
@PropertySource用于加载.properties 文件中的配置

6. Spring集成Junit

每个测试方法都有加载配置文件和从配置文件获取bean的代码

解决思路

•让SpringJunit负责创建Spring容器,但是需要将配置文件的名称告诉它

•将需要进行测试Bean直接在测试类中进行注入

集成步骤:

①导入spring集成Junit的坐标

<!--此处需要注意的是,spring5 及以上版本要求 junit 的版本必须是 4.12 及以上-->
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-test</artifactId>   
    <version>5.0.2.RELEASE</version>
</dependency>

<dependency>
    <groupId>junit</groupId>
    <artifactId>junit</artifactId>
    <version>4.12</version> 
    <scope>test</scope>
</dependency>

②使用@Runwith注解替换原来的运行期

@RunWith(SpringJUnit4ClassRunner.**class**)
 public class SpringJunitTest {
 }

③使用@ContextConfiguration指定配置文件或配置类

@RunWith(SpringJUnit4ClassRunner.class)
//加载spring核心配置文件
//@ContextConfiguration(value = {"classpath:applicationContext.xml"})
//加载spring核心配置类
@ContextConfiguration(classes = {SpringConfiguration.class})
public class SpringJunitTest {}

④使用@Autowired注入需要测试的对象

⑤创建测试方法进行测试

7. AOP配置

动态代理

常用的动态代理技术

JDK 代理 : 基于接口的动态代理技术

cglib 代理:基于父类的动态代理技术

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-8NJa8Res-1625401967487)(G:\PMY\专业资料\SSM\03-就业课(2.1)]-Spring\day03_ AOP简介\笔记\img\图片1.png)

JDK 的动态代理

①目标类接口

public interface TargetInterface {
   public void method();
}

②目标类

public class Target implements TargetInterface {
    @Override
    public void method() {
        System.out.println("Target running....");
    }
}

③动态代理代码

Target target = new Target(); //创建目标对象
//创建代理对象
TargetInterface proxy = (TargetInterface) Proxy.newProxyInstance(target.getClass()
.getClassLoader(),target.getClass().getInterfaces(),new InvocationHandler() {
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) 
            throws Throwable {
                System.out.println("前置增强代码...");
                Object invoke = method.invoke(target, args);
                System.out.println("后置增强代码...");
                return invoke;
            }
        }
);

④ 调用代理对象的方法测试

// 测试,当调用接口的任何方法时,代理对象的代码都无序修改
proxy.method();

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-2TtuPAP6-1625401967487)(G:\PMY\专业资料\SSM\03-就业课(2.1)]-Spring\day03_ AOP简介\笔记\img\图片2.png)

cglib 的动态代理

①目标类

public class Target {
  public void method() {
      System.out.println("Target running....");
  }
}

②动态代理代码

Target target = new Target(); //创建目标对象
Enhancer enhancer = new Enhancer();   //创建增强器
enhancer.setSuperclass(Target.class); //设置父类
enhancer.setCallback(new MethodInterceptor() { //设置回调
    @Override
    public Object intercept(Object o, Method method, Object[] objects, 
    MethodProxy methodProxy) throws Throwable {
        System.out.println("前置代码增强....");
        Object invoke = method.invoke(target, objects);
        System.out.println("后置代码增强....");
        return invoke;
    }
});
Target proxy = (Target) enhancer.create(); //创建代理对象

③调用代理对象的方法测试

//测试,当调用接口的任何方法时,代理对象的代码都无序修改
proxy.method();

1. AOP相关概念

  • Target(目标对象):代理的目标对象

  • Proxy (代理):一个类被 AOP 织入增强后,就产生一个结果代理类

  • Joinpoint(连接点):所谓连接点是指那些被拦截到的点。在spring中,这些点指的是方法,因为spring只支持方法类型的连接点

  • Pointcut(切入点):所谓切入点是指我们要对哪些 Joinpoint 进行拦截的定义

  • Advice(通知/ 增强):所谓通知是指拦截到 Joinpoint 之后所要做的事情就是通知

  • Aspect(切面):是切入点和通知(引介)的结合

  • Weaving(织入):是指把增强应用到目标对象来创建新的代理对象的过程。spring采用动态代理织入,而AspectJ采用编译期织入和类装载期织入

    AOP 技术实现的内容

Spring 框架监控切入点方法的执行。一旦监控到切入点方法被运行,使用代理机制,动态创建目标对象的代理对象,根据通知类别,在代理对象的对应位置,将通知对应的功能织入,完成完整的代码逻辑运行。

AOP 底层使用哪种代理方式

在 spring 中,框架会根据目标类是否实现了接口来决定采用哪种动态代理的方式。

2. 基于 XML 的 AOP 开发

步骤:

①导入 AOP 相关坐标

<!--导入spring的context坐标,context依赖aop-->
<dependency>
  <groupId>org.springframework</groupId>
  <artifactId>spring-context</artifactId>
  <version>5.0.5.RELEASE</version>
</dependency>
<!-- aspectj的织入 -->
<dependency>
  <groupId>org.aspectj</groupId>
  <artifactId>aspectjweaver</artifactId>
  <version>1.8.13</version>
</dependency>

②创建目标接口和目标类(内部有切点)

public interface TargetInterface {
    public void method();
}

public class Target implements TargetInterface {
    @Override
    public void method() {
        System.out.println("Target running....");
    }
}

③创建切面类(内部有增强方法)

public class MyAspect {
    //前置增强方法
    public void before(){
        System.out.println("前置代码增强.....");
    }
}

④将目标类和切面类的对象创建权交给 spring

<!--配置目标类-->
<bean id="target" class="com.itheima.aop.Target"></bean>
<!--配置切面类-->
<bean id="myAspect" class="com.itheima.aop.MyAspect"></bean>

⑤在 applicationContext.xml 中配置织入关系

导入aop命名空间

<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/context
        http://www.springframework.org/schema/context/spring-context.xsd
        http://www.springframework.org/schema/aop
        http://www.springframework.org/schema/aop/spring-aop.xsd
        http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans.xsd">

⑤在 applicationContext.xml 中配置织入关系

配置切点表达式和前置增强的织入关系

<aop:config>
    <!--引用myAspect的Bean为切面对象-->
    <aop:aspect ref="myAspect">
        <!--配置Target的method方法执行时要进行myAspect的before方法前置增强-->
        <aop:before method="before" pointcut="execution(public void com.itheima.aop.Target.method())"></aop:before>
    </aop:aspect>
</aop:config>

⑥测试代码

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:applicationContext.xml")
public class AopTest {
    @Autowired
    private TargetInterface target;
    @Test
    public void test1(){
        target.method();
    }
}
2.1 XML 配置 AOP 详解
1)表达式语法:
execution([修饰符] 返回值类型 包名.类名.方法名(参数))
  • 访问修饰符可以省略

  • 返回值类型、包名、类名、方法名可以使用星号* 代表任意

  • 包名与类名之间一个点 . 代表当前包下的类,两个点 … 表示当前包及其子包下的类

  • 参数列表可以使用两个点 … 表示任意个数,任意类型的参数列表

2) 通知的类型

通知的配置语法:

<aop:通知类型 method=“切面类中方法名” pointcut=“切点表达式"></aop:通知类型>

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-4ROZC0Dn-1625401967490)(G:\PMY\专业资料\SSM\03-就业课(2.1)]-Spring\day03_ AOP简介\笔记\img\图片5.png)

3) 切点表达式的抽取

当多个增强的切点表达式相同时,可以将切点表达式进行抽取,在增强中使用 pointcut-ref 属性代替 pointcut 属性来引用抽取后的切点表达式。

<aop:config>
    <!--引用myAspect的Bean为切面对象-->
    <aop:aspect ref="myAspect">
        <aop:pointcut id="myPointcut" expression="execution(* com.itheima.aop.*.*(..))"/>
        <aop:before method="before" pointcut-ref="myPointcut"></aop:before>
    </aop:aspect>
</aop:config>
2.2 知识要点
  • aop织入的配置
<aop:config>
    <aop:aspect ref=“切面类”>
        <aop:before method=“通知方法名称” pointcut="切点表达式">					</aop:before>
</aop:config>
  • 通知的类型:前置通知、后置通知、环绕通知、异常抛出通知、最终通知
  • 切点表达式的写法:
execution([修饰符] 返回值类型 包名.类名.方法名(参数))

3.基于注解的 AOP 开发

3.1注解配置步骤

①创建目标接口和目标类(内部有切点)

②创建切面类(内部有增强方法)

③将目标类和切面类的对象创建权交给 spring

@Component("target")
public class Target implements TargetInterface {
    @Override
    public void method() {
        System.out.println("Target running....");
    }
}
@Component("myAspect")
public class MyAspect {
    public void before(){
        System.out.println("前置代码增强.....");
    }
}

④在切面类中使用注解配置织入关系

@Component("myAspect")
@Aspect
public class MyAspect {
    @Before("execution(* com.itheima.aop.*.*(..))")
    public void before(){
        System.out.println("前置代码增强.....");
    }
}

⑤在配置文件中开启组件扫描和 AOP 的自动代理

<!--组件扫描-->
<context:component-scan base-package="com.itheima.aop"/>

<!--aop的自动代理-->
<aop:aspectj-autoproxy></aop:aspectj-autoproxy>

⑥测试代码

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:applicationContext.xml")
public class AopTest {
    @Autowired
    private TargetInterface target;
    @Test
    public void test1(){
        target.method();
    }
}
3.2 注解配置 AOP 详解
1) 注解通知的类型

通知的配置语法:@通知注解(“切点表达式")

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-r6uJdsm0-1625401967491)(G:\PMY\专业资料\SSM\03-就业课(2.1)]-Spring\day03_ AOP简介\笔记\img\图片7.png)

2) 切点表达式的抽取

同 xml配置
aop 一样,我们可以将切点表达式抽取。抽取方式是在切面内定义方法,在该方法上使用@Pointcut注解定义切点表达式,然后在在增强注解中进行引用。具体如下:

@@Component("myAspect")
@Aspect
public class MyAspect {
    @Before("MyAspect.myPoint()")
    public void before(){
        System.out.println("前置代码增强.....");
    }
    @Pointcut("execution(* com.itheima.aop.*.*(..))")
    public void myPoint(){}
}
3.3 知识要点
  • 注解aop开发步骤

①使用@Aspect标注切面类

②使用@通知注解标注通知方法

③在配置文件中配置aop自动代理aop:aspectj-autoproxy/

  • 通知注解类型

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-EgAPcAEk-1625401967492)(G:\PMY\专业资料\SSM\03-就业课(2.1)]-Spring\day03_ AOP简介\笔记\img\图片8.png)

4. 声明式事务

1. 编程式事务控制相关对象

1.1 PlatformTransactionManager

PlatformTransactionManager 接口是 spring 的事务管理器,它里面提供了我们常用的操作事务的方法。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-mhJng4sw-1625401967493)(G:\PMY\专业资料\SSM\03-就业课(2.1)]-Spring\day04_ JdbcTemplate基本使用\笔记\img\2.png)

注意:

PlatformTransactionManager 是接口类型,不同的 Dao 层技术则有不同的实现类,例如:Dao 层技术是jdbc 或 mybatis 时:org.springframework.jdbc.datasource.DataSourceTransactionManager

Dao 层技术是hibernate时:org.springframework.orm.hibernate5.HibernateTransactionManager

1.2 TransactionDefinition

TransactionDefinition 是事务的定义信息对象,里面有如下方法:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-bghO4yFE-1625401967493)(G:\PMY\专业资料\SSM\03-就业课(2.1)]-Spring\day04_ JdbcTemplate基本使用\笔记\img\3.png)

1. 事务隔离级别

设置隔离级别,可以解决事务并发产生的问题,如脏读、不可重复读和虚读。

  • ISOLATION_DEFAULT

  • ISOLATION_READ_UNCOMMITTED

  • ISOLATION_READ_COMMITTED

  • ISOLATION_REPEATABLE_READ

  • ISOLATION_SERIALIZABLE

2. 事务传播行为
  • REQUIRED:如果当前没有事务,就新建一个事务,如果已经存在一个事务中,加入到这个事务中。一般的选择(默认值)

  • SUPPORTS:支持当前事务,如果当前没有事务,就以非事务方式执行(没有事务)

  • MANDATORY:使用当前的事务,如果当前没有事务,就抛出异常

  • REQUERS_NEW:新建事务,如果当前在事务中,把当前事务挂起。

  • NOT_SUPPORTED:以非事务方式执行操作,如果当前存在事务,就把当前事务挂起

  • NEVER:以非事务方式运行,如果当前存在事务,抛出异常

  • NESTED:如果当前存在事务,则在嵌套事务内执行。如果当前没有事务,则执行 REQUIRED 类似的操作

  • 超时时间:默认值是-1,没有超时限制。如果有,以秒为单位进行设置

  • 是否只读:建议查询时设置为只读

1.3 TransactionStatus

TransactionStatus 接口提供的是事务具体的运行状态,方法介绍如下。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-TYaCwadN-1625401967494)(G:\PMY\专业资料\SSM\03-就业课(2.1)]-Spring\day04_ JdbcTemplate基本使用\笔记\img\4.png)

1.4 知识要点

编程式事务控制三大对象

  • PlatformTransactionManager

  • TransactionDefinition

  • TransactionStatus

2. 基于 XML 的声明式事务控制

2.2 声明式事务控制的实现

声明式事务底层就是AOP

声明式事务控制明确事项:

  • 谁是切点?

  • 谁是通知?

  • 配置切面?

①引入tx命名空间

<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/context
        http://www.springframework.org/schema/context/spring-context.xsd
        http://www.springframework.org/schema/aop
        http://www.springframework.org/schema/aop/spring-aop.xsd
        http://www.springframework.org/schema/tx 
        http://www.springframework.org/schema/tx/spring-tx.xsd
        http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans.xsd">

②配置事务增强

<!--平台事务管理器-->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
    <property name="dataSource" ref="dataSource"></property>
</bean>

<!--事务增强配置-->
<tx:advice id="txAdvice" transaction-manager="transactionManager">
    <tx:attributes>
        <tx:method name="*"/>
    </tx:attributes>
</tx:advice>

③配置事务 AOP 织入

<!--事务的aop增强-->
<aop:config>
    <aop:pointcut id="myPointcut" expression="execution(* com.itheima.service.impl.*.*(..))"/>
    <aop:advisor advice-ref="txAdvice" pointcut-ref="myPointcut"></aop:advisor>
</aop:config>

④测试事务控制转账业务代码

@Override
public void transfer(String outMan, String inMan, double money) {
    accountDao.out(outMan,money);
    int i = 1/0;
    accountDao.in(inMan,money);
}
2.3 切点方法的事务参数的配置
<!--事务增强配置-->
<tx:advice id="txAdvice" transaction-manager="transactionManager">
    <tx:attributes>
        <tx:method name="*"/>
    </tx:attributes>
</tx:advice>

其中,tx:method 代表切点方法的事务参数的配置,例如:

<tx:method name="transfer" isolation="REPEATABLE_READ" propagation="REQUIRED" timeout="-1" read-only="false"/>
  • name:切点方法名称

  • isolation:事务的隔离级别

  • propogation:事务的传播行为

  • timeout:超时时间

  • read-only:是否只读

2.4 知识要点

声明式事务控制的配置要点

  • 平台事务管理器配置

  • 事务通知的配置

  • 事务aop织入的配置

3. 基于注解的声明式事务控制

3.1 使用注解配置声明式事务控制
  1. 编写 AccoutDao
@Repository("accountDao")
public class AccountDaoImpl implements AccountDao {
    @Autowired
    private JdbcTemplate jdbcTemplate;
    public void out(String outMan, double money) {
        jdbcTemplate.update("update account set money=money-? where name=?",money,outMan);
    }
    public void in(String inMan, double money) {
        jdbcTemplate.update("update account set money=money+? where name=?",money,inMan);
    }
}
  1. 编写 AccoutService
@Service("accountService")
@Transactional
public class AccountServiceImpl implements AccountService {
    @Autowired
    private AccountDao accountDao;
    @Transactional(isolation = Isolation.READ_COMMITTED,propagation = Propagation.REQUIRED)
    public void transfer(String outMan, String inMan, double money) {
        accountDao.out(outMan,money);
        int i = 1/0;
        accountDao.in(inMan,money);
    }
}
  1. 编写 applicationContext.xml 配置文件
<!—之前省略datsSource、jdbcTemplate、平台事务管理器的配置-->
<!--组件扫描-->
<context:component-scan base-package="com.itheima"/>
<!--事务的注解驱动-->
<tx:annotation-driven/>
3.2 注解配置声明式事务控制解析

①使用 @Transactional 在需要进行事务控制的类或是方法上修饰,注解可用的属性同 xml 配置方式,例如隔离级别、传播行为等。

②注解使用在类上,那么该类下的所有方法都使用同一套注解参数配置。

③使用在方法上,不同的方法可以采用不同的事务参数配置。

④Xml配置文件中要开启事务的注解驱动<tx:annotation-driven />

3.3 知识要点

注解声明式事务控制的配置要点

  • 平台事务管理器配置(xml方式)

  • 事务通知的配置(@Transactional注解配置)

  • 事务注解驱动的配置 tx:annotation-driven/

SpringMVC

1. Spring与Web环境集成

1.1 ApplicationContext应用上下文获取方式

应用上下文对象是通过new ClasspathXmlApplicationContext(spring配置文件) 方式获取的,但是每次从容器中获得Bean时都要编写new ClasspathXmlApplicationContext(spring配置文件) ,这样的弊端是配置文件加载多次,应用上下文对象创建多次。

在Web项目中,可以使用ServletContextListener监听Web应用的启动,我们可以在Web应用启动时,就加载Spring的配置文件,创建应用上下文对象ApplicationContext,在将其存储到最大的域servletContext域中,这样就可以在任意位置从域中获得应用上下文ApplicationContext对象了。

1.2 Spring提供获取应用上下文的工具

上面的分析不用手动实现,Spring提供了一个监听器ContextLoaderListener就是对上述功能的封装,该监听器内部加载Spring配置文件,创建应用上下文对象,并存储到ServletContext域中,提供了一个客户端工具WebApplicationContextUtils供使用者获得应用上下文对象。

所以我们需要做的只有两件事:

①在web.xml中配置ContextLoaderListener监听器(导入spring-web坐标)

②使用WebApplicationContextUtils获得应用上下文对象ApplicationContext

1.3 导入Spring集成web的坐标

<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-web</artifactId>
    <version>5.0.5.RELEASE</version>
</dependency>

1.4 配置ContextLoaderListener监听器

<!--全局参数-->
<context-param>
    <param-name>contextConfigLocation</param-name>
    <param-value>classpath:applicationContext.xml</param-value>
</context-param>
<!--Spring的监听器-->
<listener>
	<listener-class>
       org.springframework.web.context.ContextLoaderListener
   </listener-class>
 </listener>

1.5 通过工具获得应用上下文对象

ApplicationContext applicationContext =    
    WebApplicationContextUtils.getWebApplicationContext(servletContext);
    Object obj = applicationContext.getBean("id");

知识要点

Spring集成web环境步骤

​ ①配置ContextLoaderListener监听器

​ ②使用WebApplicationContextUtils获得应用上下文

2. SpringMVC的简介

2.1 Spring开发步骤

需求:客户端发起请求,服务器端接收请求,执行逻辑并进行视图跳转。

开发步骤

①导入Spring和SpringMVC的坐标、导入Servlet和Jsp的坐标

②在web.xml配置SpringMVC的核心控制器

<servlet>
    <servlet-name>DispatcherServlet</servlet-name>
    <servlet-class>
        org.springframework.web.servlet.DispatcherServlet
    </servlet-class>  
    <init-param>
        <!--springmvc的配置文件-->
        <param-name>contextConfigLocation</param-name>
        <param-value>classpath:spring-mvc.xml</param-value>
    </init-param>
    	<!--服务器已启动就开始-->
	<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>   
    <servlet-name>DispatcherServlet</servlet-name>
    <url-pattern>/</url-pattern>
</servlet-mapping>

③创建Controller和业务方法

public class QuickController {
	public String quickMethod(){
		System.out.println("quickMethod running.....");
		return "index";
	}
}

③创建视图页面index.jsp

④配置注解

@Controller
public class QuickController {
	@RequestMapping("/quick")
	public String quickMethod(){
		System.out.println("quickMethod running.....");
			return "index";
	}
}

⑤创建spring-mvc.xml

<beans xmlns="http://www.springframework.org/schema/beans"  
    xmlns:mvc="http://www.springframework.org/schema/mvc"
    xmlns:context="http://www.springframework.org/schema/context" 
    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 
    http://www.springframework.org/schema/mvc   
    http://www.springframework.org/schema/mvc/spring-mvc.xsd  
    http://www.springframework.org/schema/context   
    http://www.springframework.org/schema/context/spring-context.xsd">
    <!--配置注解扫描-->
    <context:component-scan base-package="com.itheima"/>
</beans>

⑥访问测试地址

2.2 SpringMVC流程图示

步骤

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-nzfJkmHA-1625401967494)(G:\PMY\专业资料\SSM\04-就业课(2.1)]-SpringMVC\day01_Spring集成web开发环境\笔记\01-SpringMVC入门步骤.png)

代码角度的访问流程

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-stytQDTo-1625401967495)(G:\PMY\专业资料\SSM\04-就业课(2.1)]-SpringMVC\day01_Spring集成web开发环境\笔记\02-Spring访问流程(代码角度).png)

2.3 知识要点

SpringMVC的开发步骤

①导入SpringMVC相关坐标

②配置SpringMVC核心控制器DispathcerServlet

③创建Controller类和视图页面

④使用注解配置Controller类中业务方法的映射地址

⑤配置SpringMVC核心文件 spring-mvc.xml

⑥客户端发起请求测试

3. SpringMVC的组件解析

3.1 SpringMVC的执行流程

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-0EmAwjRY-1625401967495)(C:\Users\PMY\AppData\Roaming\Typora\typora-user-images\image-20210427105735495.png)]

①用户发送请求至前端控制器DispatcherServlet。

②DispatcherServlet收到请求调用HandlerMapping处理器映射器。

③处理器映射器找到具体的处理器(可以根据xml配置、注解进行查找),生成处理器对象及处理器拦截器(如果有则生成)一并返回给DispatcherServlet。

④DispatcherServlet调用HandlerAdapter处理器适配器。

⑤HandlerAdapter经过适配调用具体的处理器(Controller,也叫后端控制器)。

⑥Controller执行完成返回ModelAndView。

⑦HandlerAdapter将controller执行结果ModelAndView返回给DispatcherServlet。

⑧DispatcherServlet将ModelAndView传给ViewReslover视图解析器。

⑨ViewReslover解析后返回具体View。

⑩DispatcherServlet根据View进行渲染视图(即将模型数据填充至视图中)。DispatcherServlet响应用户。

3.2 SpringMVC组件解析

  1. 前端控制器:DispatcherServlet

​ 用户请求到达前端控制器,它就相当于 MVC 模式中的 C,DispatcherServlet 是整个流程控制的中心,由它调用其它组件处理用户的请求,DispatcherServlet 的存在降低了组件之间的耦合性。

  1. 处理器映射器:HandlerMapping

​ HandlerMapping 负责根据用户请求找到 Handler 即处理器,SpringMVC 提供了不同的映射器实现不同的映射方式,例如:配置文件方式,实现接口方式,注解方式等。

  1. 处理器适配器:HandlerAdapter

​ 通过 HandlerAdapter 对处理器进行执行,这是适配器模式的应用,通过扩展适配器可以对更多类型的处理器进行执行。

  1. 处理器:Handler

​ 它就是我们开发中要编写的具体业务控制器。由 DispatcherServlet 把用户请求转发到 Handler。由Handler 对具体的用户请求进行处理。

  1. 视图解析器:View Resolver

​ View Resolver 负责将处理结果生成 View 视图,View Resolver 首先根据逻辑视图名解析成物理视图名,即具体的页面地址,再生成 View 视图对象,最后对 View 进行渲染将处理结果通过页面展示给用户。

  1. 视图:View

​ SpringMVC 框架提供了很多的 View 视图类型的支持,包括:jstlView、freemarkerView、pdfView等。最常用的视图就是 jsp。一般情况下需要通过页面标签或页面模版技术将模型数据通过页面展示给用户,需要由程序员根据业务需求开发具体的页面

3.3 SpringMVC注解解析

@RequestMapping

作用:用于建立请求 URL 和处理请求方法之间的对应关系

位置:

​ 类上,请求URL 的第一级访问目录。此处不写的话,就相当于应用的根目录

​ 方法上,请求 URL 的第二级访问目录,与类上的使用@ReqquestMapping标注的一级目录一起组成访问虚拟路径

属性:

​ value:用于指定请求的URL。它和path属性的作用是一样的

​ method:用于指定请求的方式

​ params:用于指定限制请求参数的条件。它支持简单的表达式。要求请求参数的key和value必须和配置的一模一样

例如:

​ params = {“accountName”},表示请求参数必须有accountName

​ params = {“moeny!100”},表示请求参数中money不能是100

1.mvc命名空间引入

命名空间:xmlns:context="http://www.springframework.org/schema/context"
        xmlns:mvc="http://www.springframework.org/schema/mvc"
约束地址:http://www.springframework.org/schema/context
        http://www.springframework.org/schema/context/spring-context.xsd
        http://www.springframework.org/schema/mvc 
        http://www.springframework.org/schema/mvc/spring-mvc.xsd

组件扫描

SpringMVC基于Spring容器,所以在进行SpringMVC操作时,需要将Controller存储到Spring容器中,如果使用@Controller注解标注的话,就需要使用<context:component-scan base-package=“com.itheima.controller"/>进行组件扫描。

3.4 SpringMVC的XML配置解析

SpringMVC有默认组件配置,默认组件都是DispatcherServlet.properties配置文件中配置的,该配置文件地址org/springframework/web/servlet/DispatcherServlet.properties,该文件中配置了默认的视图解析器,如下:

org.springframework.web.servlet.ViewResolver=org.springframework.web.servlet.view.InternalResourceViewResolver

翻看该解析器源码,可以看到该解析器的默认设置,如下:

REDIRECT_URL_PREFIX = "redirect:"  --重定向前缀
FORWARD_URL_PREFIX = "forward:"    --转发前缀(默认值)
prefix = "";     --视图名称前缀
suffix = "";     --视图名称后缀
  1. 视图解析器

我们可以通过属性注入的方式修改视图的的前后缀

<!--配置内部资源视图解析器-->
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
  <property name="prefix" value="/WEB-INF/views/"></property>
  <property name="suffix" value=".jsp"></property>
</bean>

3.5 知识要点

SpringMVC的相关组件

前端控制器:DispatcherServlet

处理器映射器:HandlerMapping

处理器适配器:HandlerAdapter

处理器:Handler

视图解析器:View Resolver

视图:View

SpringMVC的注解和配置

请求映射注解:@RequestMapping

视图解析器配置:

REDIRECT_URL_PREFIX = “redirect:”

FORWARD_URL_PREFIX = “forward:”

prefix = “”;

suffix = “”;

4. SpringMVC的数据响应

01-SpringMVC的数据响应-数据响应方式(理解)

  1. 页面跳转

直接返回字符串

通过ModelAndView对象返回

2) 回写数据

直接返回字符串

返回对象或集合

02-SpringMVC的数据响应-页面跳转-返回字符串形式(应用)

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-rzjmOHXe-1625401967495)(G:\PMY\专业资料\SSM\04-就业课(2.1)]-SpringMVC\day02_SpringMVC的数据响应\笔记\img\1.jpg)

03-SpringMVC的数据响应-页面跳转-返回ModelAndView形式1(应用)

在Controller中方法返回ModelAndView对象,并且设置视图名称

@RequestMapping(value="/quick2")
    public ModelAndView save2(){
        /*
            Model:模型 作用封装数据
            View:视图 作用展示数据
         */
        ModelAndView modelAndView = new ModelAndView();
        //设置模型数据
        modelAndView.addObject("username","itcast");
        //设置视图名称
        modelAndView.setViewName("success");

        return modelAndView;
    }

04-SpringMVC的数据响应-页面跳转-返回ModelAndView形式2(应用)

n在Controller中方法形参上直接声明ModelAndView,无需在方法中自己创建,在方法中直接使用该对象设置视图,同样可以跳转页面

 @RequestMapping(value="/quick3")
    public ModelAndView save3(ModelAndView modelAndView){
        modelAndView.addObject("username","itheima");
        modelAndView.setViewName("success");
        return modelAndView;
    }
@RequestMapping(value="/quick4")
    public String save4(Model model){
        model.addAttribute("username","博学谷");
        return "success";
    }

05-SpringMVC的数据响应-页面跳转-返回ModelAndView3(应用)

在Controller方法的形参上可以直接使用原生的HttpServeltRequest对象,只需声明即可

@RequestMapping(value="/quick5")
    public String save5(HttpServletRequest request){
        request.setAttribute("username","酷丁鱼");
        return "success";
    }

06-SpringMVC的数据响应-回写数据-直接回写字符串(应用)

通过SpringMVC框架注入的response对象,使用response.getWriter().print(“hello world”) 回写数据,此时不需要视图跳转,业务方法返回值为void

将需要回写的字符串直接返回,但此时需要通过@ResponseBody注解告知SpringMVC框架,方法返回的字符串不是跳转是直接在http响应体中返回

@RequestMapping(value="/quick7")
    @ResponseBody  //告知SpringMVC框架 不进行视图跳转 直接进行数据响应
    public String save7() throws IOException {
        return "hello itheima";
    }

    @RequestMapping(value="/quick6")
    public void save6(HttpServletResponse response) throws IOException {
        response.getWriter().print("hello itcast");
    }

07-SpringMVC的数据响应-回写数据-直接回写json格式字符串(应用)

@RequestMapping(value="/quick8")
    @ResponseBody
    public String save8() throws IOException {
        return "{\"username\":\"zhangsan\",\"age\":18}";
    }

手动拼接json格式字符串的方式很麻烦,开发中往往要将复杂的java对象转换成json格式的字符串,我们可以使用web阶段学习过的json转换工具jackson进行转换,通过jackson转换json格式字符串,回写字符串

@RequestMapping(value="/quick9")
    @ResponseBody
    public String save9() throws IOException {
        User user = new User();
        user.setUsername("lisi");
        user.setAge(30);
        //使用json的转换工具将对象转换成json格式字符串在返回
        ObjectMapper objectMapper = new ObjectMapper();
        String json = objectMapper.writeValueAsString(user);

        return json;
    }

08-SpringMVC的数据响应-回写数据-返回对象或集合(应用)

通过SpringMVC帮助我们对对象或集合进行json字符串的转换并回写,为处理器适配器配置消息转换参数,指定使用jackson进行对象或集合的转换,因此需要在spring-mvc.xml中进行如下配置:

<bean class="org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter">
        <property name="messageConverters">
            <list>
                <bean class="org.springframework.http.converter.json.MappingJackson2HttpMessageConverter"/>
            </list>
        </property>
    </bean>
@RequestMapping(value="/quick10")
    @ResponseBody
    //期望SpringMVC自动将User转换成json格式的字符串
    public User save10() throws IOException {
        User user = new User();
        user.setUsername("lisi2");
        user.setAge(32);
        return user;
    }

09-SpringMVC的数据响应-回写数据-返回对象或集合2(应用)

在方法上添加@ResponseBody就可以返回json格式的字符串,但是这样配置比较麻烦,配置的代码比较多,因此,我们可以使用mvc的注解驱动代替上述配置

<mvc:annotation-driven/>

在 SpringMVC 的各个组件中,处理器映射器、处理器适配器、视图解析器称为 SpringMVC 的三大组件。

使用<mvc:annotation-driven />自动加载 RequestMapping、HandlerMapping(处理映射器)和RequestMappingHandlerAdapter( 处 理 适 配 器 ),可用在Spring-xml.xml配置文件中使用<mvc:annotation-driven />替代注解处理器和适配器的配置。

同时使用<mvc:annotation-driven />

默认底层就会集成jackson进行对象或集合的json格式字符串的转换

10-SpringMVC的数据响应-知识要点小结(理解,记忆)

1) 页面跳转

直接返回字符串

通过ModelAndView对象返回

2) 回写数据

直接返回字符串

HttpServletResponse 对象直接写回数据,HttpServletRequest对象带回数据,Model对象带回数据或者@ResponseBody将字符串数据写回

返回对象或集合

@ResponseBody+<mvc:annotation-driven/>

5. SpringMVC的请求

11-SpringMVC的请求-获得请求参数-请求参数类型(理解)

客户端请求参数的格式是:name=value&name=value……

服务器端要获得请求的参数,有时还需要进行数据的封装,SpringMVC可以接收如下类型的参数

基本类型参数

POJO类型参数

数组类型参数

集合类型参数

12-SpringMVC的请求-获得请求参数-获得基本类型参数(应用)

Controller中的业务方法的参数名称要与请求参数的name一致,参数值会自动映射匹配。并且能自动做类型转换;

自动的类型转换是指从String向其他类型的转换

http://localhost:8080/itheima_springmvc1/quick9?username=zhangsan&age=12

@RequestMapping(value="/quick11")
    @ResponseBody
    public void save11(String username,int age) throws IOException {
        System.out.println(username);
        System.out.println(age);
    }

13-SpringMVC的请求-获得请求参数-获得POJO类型参数(应用)

Controller中的业务方法的POJO参数的属性名与请求参数的name一致,参数值会自动映射匹配。

package com.itheima.domain;

public class User {

    private String username;
    private int age;

    public String getUsername() {
        return username;
    }

    public void setUsername(String username) {
        this.username = username;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    @Override
    public String toString() {
        return "User{" +
                "username='" + username + '\'' +
                ", age=" + age +
                '}';
    }
}

@RequestMapping(value="/quick12")
    @ResponseBody
    public void save12(User user) throws IOException {
        System.out.println(user);
    }

14-SpringMVC的请求-获得请求参数-获得数组类型参数(应用)

Controller中的业务方法数组名称与请求参数的name一致,参数值会自动映射匹配。

@RequestMapping(value="/quick13")
    @ResponseBody
    public void save13(String[] strs) throws IOException {
        System.out.println(Arrays.asList(strs));
    }

15-SpringMVC的请求-获得请求参数-获得集合类型参数1(应用)

获得集合参数时,要将集合参数包装到一个POJO中才可以。

<form action="${pageContext.request.contextPath}/user/quick14" method="post">
        <%--表明是第一个User对象的username age--%>
        <input type="text" name="userList[0].username"><br/>
        <input type="text" name="userList[0].age"><br/>
        <input type="text" name="userList[1].username"><br/>
        <input type="text" name="userList[1].age"><br/>
        <input type="submit" value="提交">
    </form>
package com.itheima.domain;

import java.util.List;

public class VO {

    private List<User> userList;

    public List<User> getUserList() {
        return userList;
    }

    public void setUserList(List<User> userList) {
        this.userList = userList;
    }

    @Override
    public String toString() {
        return "VO{" +
                "userList=" + userList +
                '}';
    }
}

@RequestMapping(value="/quick14")
    @ResponseBody
    public void save14(VO vo) throws IOException {
        System.out.println(vo);
    }

16-SpringMVC的请求-获得请求参数-获得集合类型参数2(应用)

当使用ajax提交时,可以指定contentType为json形式,那么在方法参数位置使用**@RequestBody**可以直接接收集合数据而无需使用POJO进行包装

<script src="${pageContext.request.contextPath}/js/jquery-3.3.1.js"></script>
    <script>
        var userList = new Array();
        userList.push({username:"zhangsan",age:18});
        userList.push({username:"lisi",age:28});

        $.ajax({
            type:"POST",
            url:"${pageContext.request.contextPath}/user/quick15",
            data:JSON.stringify(userList),
            contentType:"application/json;charset=utf-8"
        });

    </script>
@RequestMapping(value="/quick15")
    @ResponseBody
    public void save15(@RequestBody List<User> userList) throws IOException {
        System.out.println(userList);
    }

17-SpringMVC的请求-获得请求参数-静态资源访问的开启(应用)

当有静态资源需要加载时,比如jquery文件,通过谷歌开发者工具抓包发现,没有加载到jquery文件,原因是SpringMVC的前端控制器DispatcherServlet的url-pattern配置的是/,代表对所有的资源都进行过滤操作,我们可以通过以下两种方式指定放行静态资源:

•在spring-mvc.xml配置文件中指定放行的资源

<mvc:resources mapping="/js/**"location="/js/"/>

•使用<mvc:default-servlet-handler/>标签

<!--开发资源的访问-->
    <!--<mvc:resources mapping="/js/**" location="/js/"/>
    <mvc:resources mapping="/img/**" location="/img/"/>-->

    <mvc:default-servlet-handler/>

18-SpringMVC的请求-获得请求参数-配置全局乱码过滤器(应用)

当post请求时,数据会出现乱码,我们可以设置一个过滤器来进行编码的过滤。

<!--配置全局过滤的filter-->
    <filter>
        <filter-name>CharacterEncodingFilter</filter-name>
        <filter-class>
           org.springframework.web.filter.CharacterEncodingFilter
        </filter-class>
        <init-param>
            <param-name>encoding</param-name>
            <param-value>UTF-8</param-value>
        </init-param>
    </filter>
    <filter-mapping>
        <filter-name>CharacterEncodingFilter</filter-name>
        <url-pattern>/*</url-pattern>
    </filter-mapping>

19-SpringMVC的请求-获得请求参数-参数绑定注解@RequestParam(应用)

当请求的参数名称与Controller的业务方法参数名称不一致时,就需要通过@RequestParam注解显示的绑定

<form action="${pageContext.request.contextPath}/quick16" method="post">
    <input type="text" name="name"><br>
    <input type="submit" value="提交"><br>
</form>

@RequestMapping(value="/quick16")
    @ResponseBody
    public void save16(@RequestParam(value="name",required = false,defaultValue = "itcast") String username) throws IOException {
        System.out.println(username);
    }

20-SpringMVC的请求-获得请求参数-Restful风格的参数的获取(应用)

Restful是一种软件架构风格、设计风格,而不是标准,只是提供了一组设计原则和约束条件。主要用于客户端和服务器交互类的软件,基于这个风格设计的软件可以更简洁,更有层次,更易于实现缓存机制等。

Restful风格的请求是使用“url+请求方式”表示一次请求目的的,HTTP 协议里面四个表示操作方式的动词如下:

GET:用于获取资源

POST:用于新建资源

PUT:用于更新资源

DELETE:用于删除资源

例如:

/user/1 GET : 得到 id = 1 的 user

/user/1 DELETE: 删除 id = 1 的 user

/user/1 PUT: 更新 id = 1 的 user

/user POST: 新增 user

上述url地址/user/1中的1就是要获得的请求参数,在SpringMVC中可以使用占位符进行参数绑定。地址/user/1可以写成/user/{id},占位符{id}对应的就是1的值。在业务方法中我们可以使用@PathVariable注解进行占位符的匹配获取工作。

http://localhost:8080/itheima_springmvc1/quick17/zhangsan

@RequestMapping(value="/quick17/{name}")
@ResponseBody
 public void save17(@PathVariable(value="name") String username) throws IOException {
        System.out.println(username);
 }

21-SpringMVC的请求-获得请求参数-自定义类型转换器(应用)

SpringMVC 默认已经提供了一些常用的类型转换器,例如客户端提交的字符串转换成int型进行参数设置。

但是不是所有的数据类型都提供了转换器,没有提供的就需要自定义转换器,例如:日期类型的数据就需要自定义转换器。

还需要在spring-mvc.xml里面配置它

public class DateConverter implements Converter<String, Date> {
    public Date convert(String dateStr) {
        //将日期字符串转换成日期对象 返回
        SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd");
        Date date = null;
        try {
            date = format.parse(dateStr);
        } catch (ParseException e) {
            e.printStackTrace();
        }
        return date;
    }
}
@RequestMapping(value="/quick18")
    @ResponseBody
    public void save18(Date date) throws IOException {
        System.out.println(date);
    }
<mvc:annotation-driven conversion-service="conversionService"/>

22-SpringMVC的请求-获得请求参数-获得Servlet相关API(应用)

SpringMVC支持使用原始ServletAPI对象作为控制器方法的参数进行注入,常用的对象如下:

HttpServletRequest

HttpServletResponse

HttpSession

@RequestMapping(value="/quick19")
    @ResponseBody
    public void save19(HttpServletRequest request, HttpServletResponse response, HttpSession session) throws IOException {
        System.out.println(request);
        System.out.println(response);
        System.out.println(session);
    }

23-SpringMVC的请求-获得请求参数-获得请求头信息(应用)

使用@RequestHeader可以获得请求头信息,相当于web阶段学习的request.getHeader(name)

@RequestHeader注解的属性如下:

value:请求头的名称

required:是否必须携带此请求头

@RequestMapping(value="/quick20")
    @ResponseBody
    public void save20(@RequestHeader(value = "User-Agent",required = false) String user_agent) throws IOException {
        System.out.println(user_agent);
    }

使用@CookieValue可以获得指定Cookie的值

@CookieValue注解的属性如下:

value:指定cookie的名称

required:是否必须携带此cookie

 @RequestMapping(value="/quick21")
    @ResponseBody
    public void save21(@CookieValue(value = "JSESSIONID") String jsessionId) throws IOException {
        System.out.println(jsessionId);
    }

6. SpringMVC的文件上传

1-SpringMVC文件上传

<form action="${pageContext.request.contextPath}/user/quick22" method="post" enctype="multipart/form-data">
        名称<input type="text" name="username"><br/>
        文件1<input type="file" name="uploadFile"><br/>
        <input type="submit" value="提交">
    </form>

2-文件上传的原理

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-oxRa6SrB-1625401967496)(G:\PMY\专业资料\SSM\04-就业课(2.1)]-SpringMVC\day03_SpringMVC的文件上传\笔记\img\5.jpg)

3-单文件上传的代码实现1

添加依赖

<dependency>
      <groupId>commons-fileupload</groupId>
      <artifactId>commons-fileupload</artifactId>
      <version>1.3.1</version>
    </dependency>
    <dependency>
      <groupId>commons-io</groupId>
      <artifactId>commons-io</artifactId>
      <version>2.3</version>
    </dependency>

配置多媒体解析器

<!--配置文件上传解析器-->
    <bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
        <property name="defaultEncoding" value="UTF-8"/>
        <property name="maxUploadSize" value="500000"/>
    </bean>

后台程序

@RequestMapping(value="/quick22")
    @ResponseBody
    public void save22(String username, MultipartFile uploadFile) throws IOException {
        System.out.println(username);
       	System.out.println(uploadFile);
    }

4-单文件上传的代码实现2

完成文件上传

@RequestMapping(value="/quick22")
    @ResponseBody
    public void save22(String username, MultipartFile uploadFile) throws IOException {
        System.out.println(username);
        //获得上传文件的名称
        String originalFilename = uploadFile.getOriginalFilename();
        uploadFile.transferTo(new File("C:\\upload\\"+originalFilename));
    }

5-多文件上传的代码实现

多文件上传,只需要将页面修改为多个文件上传项,将方法参数MultipartFile类型修改为MultipartFile[]即可

<form action="${pageContext.request.contextPath}/user/quick23" method="post" enctype="multipart/form-data">
        名称<input type="text" name="username"><br/>
        文件1<input type="file" name="uploadFile"><br/>
        文件2<input type="file" name="uploadFile"><br/>
        <input type="submit" value="提交">
    </form>
@RequestMapping(value="/quick23")
    @ResponseBody
    public void save23(String username, MultipartFile[] uploadFile) throws IOException {
        System.out.println(username);
        for (MultipartFile multipartFile : uploadFile) {
            String originalFilename = multipartFile.getOriginalFilename();
            multipartFile.transferTo(new File("C:\\upload\\"+originalFilename));
        }
    }

6-SpringMVC的请求-知识要点(理解,记忆)

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-do8gcOkH-1625401967497)(G:\PMY\专业资料\SSM\04-就业课(2.1)]-SpringMVC\day03_SpringMVC的文件上传\笔记\img\6.jpg)

7. SpringMVC的拦截器

01-拦截器的作用

Spring MVC 的拦截器类似于 Servlet 开发中的过滤器 Filter,用于对处理器进行预处理和后处理。

将拦截器按一定的顺序联结成一条链,这条链称为拦截器链(InterceptorChain)。在访问被拦截的方法或字段时,拦截器链中的拦截器就会按其之前定义的顺序被调用。拦截器也是AOP思想的具体实现。

02-interceptor和filter区别

关于interceptor和filter的区别,如图所示:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-YO4MXYxD-1625401967498)(G:\PMY\专业资料\SSM\04-就业课(2.1)]-SpringMVC\day03_SpringMVC的文件上传\笔记\img\1.png)

03-快速入门

自定义拦截器很简单,只有如下三步:

①创建拦截器类实现HandlerInterceptor接口

②配置拦截器

③测试拦截器的拦截效果

编写拦截器:

public class MyInterceptor1 implements HandlerInterceptor {
    //在目标方法执行之前 执行
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws ServletException, IOException {
        System.out.println("preHandle.....");
}
    //在目标方法执行之后 视图对象返回之前执行
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) {
System.out.println("postHandle...");
    }
    //在流程都执行完毕后 执行
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) {
        System.out.println("afterCompletion....");
    }
}

配置:在SpringMVC的配置文件中配置

<!--配置拦截器-->
    <mvc:interceptors>
        <mvc:interceptor>
            <!--对哪些资源执行拦截操作-->
            <mvc:mapping path="/**"/>
            <bean class="com.itheima.interceptor.MyInterceptor1"/>
        </mvc:interceptor>
    </mvc:interceptors>

编写测试程序测试:

编写Controller,发请求到controller,跳转页面

@Controller
public class TargetController {
    @RequestMapping("/target")
    public ModelAndView show(){
        System.out.println("目标资源执行......");
        ModelAndView modelAndView = new ModelAndView();
        modelAndView.addObject("name","itcast");
        modelAndView.setViewName("index");
        return modelAndView;
    }
}

页面

04-入门详解

拦截器在预处理后什么情况下会执行目标资源,什么情况下不执行目标资源,以及在有多个拦截器的情况下拦截器的执行顺序是什么?

结论:

当拦截器的preHandle方法返回true则会执行目标资源,如果返回false则不执行目标资源

多个拦截器情况下,配置在前的先执行,配置在后的后执行

拦截器中的方法执行顺序是:preHandler-------目标资源----postHandle---- afterCompletion

05-知识小结

拦截器中的方法说明如下

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-lLwB9YYg-1625401967499)(G:\PMY\专业资料\SSM\04-就业课(2.1)]-SpringMVC\day03_SpringMVC的文件上传\笔记\img\2.png)

8. SpringMVC异常处理机制

1.1 异常处理的思路

系统中异常包括两类:预期异常和运行时异常RuntimeException,前者通过捕获异常从而获取异常信息,后者主要通过规范代码开发、测试等手段减少运行时异常的发生。

系统的Dao、Service、Controller出现都通过throws Exception向上抛出,最后由SpringMVC前端控制器交由异常处理器进行异常处理,如下图:

1.2 异常处理两种方式

① 使用Spring MVC提供的简单异常处理器SimpleMappingExceptionResolver

② 实现Spring的异常处理接口HandlerExceptionResolver 自定义自己的异常处理器

1.3 简单异常处理器SimpleMappingExceptionResolver

SpringMVC已经定义好了该类型转换器,在使用时可以根据项目情况进行相应异常与视图的映射配置

<!--配置简单映射异常处理器-->
    <bean class=“org.springframework.web.servlet.handler.SimpleMappingExceptionResolver”>    
    <property name=“defaultErrorView” value=“error”/>   默认错误视图
    <property name=“exceptionMappings”>
        <map>		异常类型		                             错误视图
            <entry key="com.itheima.exception.MyException" 									value="error"/>
            <entry key="java.lang.ClassCastException" value="error"/>
        </map>
    </property>
</bean>

1.4 自定义异常处理步骤

①创建异常处理器类实现HandlerExceptionResolver

public class MyExceptionResolver implements HandlerExceptionResolver {
@Override
public ModelAndView resolveException(HttpServletRequest request, 
    HttpServletResponse response, Object handler, Exception ex) {
    //处理异常的代码实现
    //创建ModelAndView对象
    ModelAndView modelAndView = new ModelAndView(); 
    modelAndView.setViewName("exceptionPage");
    return modelAndView;
    }
}

②配置异常处理器

<bean id="exceptionResolver"        
      class="com.itheima.exception.MyExceptionResolver"/>

③编写异常页面

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
	<title>Title</title>
</head>
<body>
	这是一个最终异常的显示页面
</body>
</html>

④测试异常跳转

@RequestMapping("/quick22")
@ResponseBody
public void quickMethod22() throws IOException, ParseException {
    SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd"); 
    simpleDateFormat.parse("abcde");
}

1.5 知识要点

异常处理方式

配置简单异常处理器SimpleMappingExceptionResolver

自定义异常处理器

自定义异常处理步骤

①创建异常处理器类实现HandlerExceptionResolver

②配置异常处理器

③编写异常页面

④测试异常跳转

Mybatis

MyBatis开发步骤:

1)导入MyBatis的坐标和其他相关坐标

<!--mybatis坐标-->
<dependency>
    <groupId>org.mybatis</groupId>
    <artifactId>mybatis</artifactId>
    <version>3.4.5</version>
</dependency>
<!--mysql驱动坐标-->
<dependency>    
    <groupId>mysql</groupId>   
    <artifactId>mysql-connector-java</artifactId>    
    <version>5.1.6</version>    
    <scope>runtime</scope>
</dependency>
<!--单元测试坐标-->
<dependency>    
    <groupId>junit</groupId>    
    <artifactId>junit</artifactId>    
    <version>4.12</version>    
    <scope>test</scope>
</dependency>
<!--日志坐标-->
<dependency>    
    <groupId>log4j</groupId>    
    <artifactId>log4j</artifactId>    
    <version>1.2.12</version>
</dependency>
  1. 创建user数据表

  2. 编写User实体

4)编写UserMapper映射文件

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper        
	PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"        
	"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="userMapper">    
	<select id="findAll" resultType="com.itheima.domain.User">        
		select * from User    
	</select>
</mapper>
  1. 编写MyBatis核心文件
<!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:///test"/>      
				<property name="username" value="root"/>
				<property name="password" value="root"/>            
			</dataSource>        
		</environment>    
	</environments>    
	
	<mappers> 
		<mapper resource="com/itheima/mapper/UserMapper.xml"/> 
	</mappers>
</configuration>

6)测试代码

//加载核心配置文件
InputStream resourceAsStream = Resources.getResourceAsStream("SqlMapConfig.xml");
//获得sqlSession工厂对象
SqlSessionFactory sqlSessionFactory = new            
                           SqlSessionFactoryBuilder().build(resourceAsStream);
//获得sqlSession对象
SqlSession sqlSession = sqlSessionFactory.openSession();
//执行sql语句
List<User> userList = sqlSession.selectList("userMapper.findAll");
//打印结果
System.out.println(userList);
//释放资源
sqlSession.close();

1. MyBatis的映射文件概述

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-5NZbCNvO-1625401967500)(G:/PMY/专业资料/SSM/06-就业课(2.1)]-Mybatis/day01_Mybatis快速入门/笔记/img/图片6.png)

2. MyBatis的增删改查操作

1)编写UserMapper映射文件

<mapper namespace="userMapper">    
	<insert id="add" parameterType="com.itheima.domain.User">        
		insert into user values(#{id},#{username},#{password})    
	</insert>
</mapper>

2)编写插入实体User的代码

//加载Mybatis的配置文件
InputStream resourceAsStream = Resources.getResourceAsStream("SqlMapConfig.xml");

SqlSessionFactory sqlSessionFactory = new                       SqlSessionFactoryBuilder().build(resourceAsStream);

//传入true的话
SqlSession sqlSession = sqlSessionFactory.openSession();

int insert = sqlSession.insert("userMapper.add", user);
System.out.println(insert);
//提交事务
sqlSession.commit();
sqlSession.close();

3)操作注意问题

• 插入语句使用insert标签

• 在映射文件中使用parameterType属性指定要插入的数据类型

•Sql语句中使用#{实体属性名}方式引用实体中的属性值

•插入操作使用的API是sqlSession.insert(“命名空间.id”,实体对象);

•插入操作涉及数据库数据变化,所以要使用sqlSession对象显示的提交事务,即sqlSession.commit()

增删改查映射配置与API:
查询数据: List<User> userList = sqlSession.selectList("userMapper.findAll");
    <select id="findAll" resultType="com.itheima.domain.User">
        select * from User
    </select>
添加数据: sqlSession.insert("userMapper.add", user);
    <insert id="add" parameterType="com.itheima.domain.User">
        insert into user values(#{id},#{username},#{password})
    </insert>
修改数据: sqlSession.update("userMapper.update", user);
    <update id="update" parameterType="com.itheima.domain.User">
        update user set username=#{username},password=#{password} where id=#{id}
    </update>
删除数据:sqlSession.delete("userMapper.delete",3);
    <delete id="delete" parameterType="java.lang.Integer">
        delete from user where id=#{id}
    </delete>

3. MyBatis核心配置文件概述

3.1 MyBatis核心配置文件层级关系

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Gaxhavjc-1625401967501)(G:/PMY/专业资料/SSM/06-就业课(2.1)]-Mybatis/day01_Mybatis快速入门/笔记/img/图片7.png)

3.2 MyBatis常用配置解析

1)environments标签

数据库环境的配置,支持多环境配置

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-hBBQAINt-1625401967502)(G:/PMY/专业资料/SSM/06-就业课(2.1)]-Mybatis/day01_Mybatis快速入门/笔记/img/图片8.png)

其中,事务管理器(transactionManager)类型有两种:

•JDBC:这个配置就是直接使用了JDBC 的提交和回滚设置,它依赖于从数据源得到的连接来管理事务作用域。

•MANAGED:这个配置几乎没做什么。它从来不提交或回滚一个连接,而是让容器来管理事务的整个生命周期(比如 JEE 应用服务器的上下文)。 默认情况下它会关闭连接,然而一些容器并不希望这样,因此需要将 closeConnection 属性设置为 false 来阻止它默认的关闭行为。

其中,数据源(dataSource)类型有三种:

•UNPOOLED:这个数据源的实现只是每次被请求时打开和关闭连接。

•POOLED:这种数据源的实现利用“池”的概念将 JDBC 连接对象组织起来。

•JNDI:这个数据源的实现是为了能在如 EJB 或应用服务器这类容器中使用,容器可以集中或在外部配置数据源,然后放置一个 JNDI 上下文的引用。

2)mapper标签

该标签的作用是加载映射的,加载方式有如下几种:

•使用相对于类路径的资源引用,例如:

•使用完全限定资源定位符(URL),例如:

•使用映射器接口实现类的完全限定类名,例如:

•将包内的映射器接口实现全部注册为映射器,例如:

3)Properties标签

实际开发中,习惯将数据源的配置信息单独抽取成一个properties文件,该标签可以加载额外配置的properties文件

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Vkj3m754-1625401967502)(G:/PMY/专业资料/SSM/06-就业课(2.1)]-Mybatis/day01_Mybatis快速入门/笔记/img/图片9.png)

4)typeAliases标签

类型别名是为Java 类型设置一个短的名字。原来的类型名称配置如下

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ltel7cOm-1625401967503)(G:/PMY/专业资料/SSM/06-就业课(2.1)]-Mybatis/day01_Mybatis快速入门/笔记/img/图片10.png)

配置typeAliases,为com.itheima.domain.User定义别名为user

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-uvfIK1yp-1625401967504)(G:/PMY/专业资料/SSM/06-就业课(2.1)]-Mybatis/day01_Mybatis快速入门/笔记/img/图片11.png)

上面我们是自定义的别名,mybatis框架已经为我们设置好的一些常用的类型的别名

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-FfRghJLw-1625401967504)(G:/PMY/专业资料/SSM/06-就业课(2.1)]-Mybatis/day01_Mybatis快速入门/笔记/img/图片12.png)

3.3 知识小结

核心配置文件常用配置:

properties标签:该标签可以加载外部的properties文件

<properties resource="jdbc.properties"></properties>

typeAliases标签:设置类型别名

<typeAlias type="com.itheima.domain.User" alias="user"></typeAlias>

mappers标签:加载映射配置

<mapper resource="com/itheima/mapper/UserMapping.xml"></mapper>

environments标签:数据源环境配置标签

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-yzZL03c8-1625401967505)(G:/PMY/专业资料/SSM/06-就业课(2.1)]-Mybatis/day01_Mybatis快速入门/笔记/img/图片13.png)

4.MyBatis相应API

4.1 SqlSession工厂构建器SqlSessionFactoryBuilder

常用API:SqlSessionFactory build(InputStream inputStream)

通过加载mybatis的核心文件的输入流的形式构建一个SqlSessionFactory对象

String resource = "org/mybatis/builder/mybatis-config.xml"; 
InputStream inputStream = Resources.getResourceAsStream(resource); 
SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder(); 
SqlSessionFactory factory = builder.build(inputStream);

其中, Resources 工具类,这个类在 org.apache.ibatis.io 包中。Resources 类帮助你从类路径下、文件系统或一个 web URL 中加载资源文件。

4.2 SqlSession工厂对象SqlSessionFactory

SqlSessionFactory 有多个个方法创建SqlSession 实例。常用的有如下两个:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Bl7ql0Xt-1625401967505)(G:/PMY/专业资料/SSM/06-就业课(2.1)]-Mybatis/day01_Mybatis快速入门/笔记/img/图片14.png)

4.3 SqlSession会话对象

SqlSession 实例在 MyBatis 中是非常强大的一个类。在这里你会看到所有执行语句、提交或回滚事务和获取映射器实例的方法。

执行语句的方法主要有:

<T> T selectOne(String statement, Object parameter) 
<E> List<E> selectList(String statement, Object parameter) 
int insert(String statement, Object parameter) 
int update(String statement, Object parameter) 
int delete(String statement, Object parameter)

操作事务的方法主要有:

void commit()  
void rollback() 

5.Mybatis的Dao层实现

5.1 传统开发方式

5.1.1编写UserDao接口
public interface UserDao {
    List<User> findAll() throws IOException;
}
5.1.2.编写UserDaoImpl实现
public class UserDaoImpl implements UserDao {
    public List<User> findAll() throws IOException {
        InputStream resourceAsStream = 
                    Resources.getResourceAsStream("SqlMapConfig.xml");
        SqlSessionFactory sqlSessionFactory = new 
                    SqlSessionFactoryBuilder().build(resourceAsStream);
        SqlSession sqlSession = sqlSessionFactory.openSession();
        List<User> userList = sqlSession.selectList("userMapper.findAll");
        sqlSession.close();
        return userList;
    }
}
5.1.3 测试传统方式
@Test
public void testTraditionDao() throws IOException {
    UserDao userDao = new UserDaoImpl();
    List<User> all = userDao.findAll();
    System.out.println(all);
}

5.2 代理开发方式

5.2.1 代理开发方式介绍

采用 Mybatis 的代理开发方式实现 DAO 层的开发,这种方式是我们后面进入企业的主流。

Mapper 接口开发方法只需要程序员编写Mapper 接口(相当于Dao 接口),由Mybatis 框架根据接口定义创建接口的动态代理对象,代理对象的方法体同上边Dao接口实现类方法。

Mapper 接口开发需要遵循以下规范:

1) Mapper.xml文件中的namespace与mapper接口的全限定名相同

2) Mapper接口方法名和Mapper.xml中定义的每个statement的id相同

3) Mapper接口方法的输入参数类型和mapper.xml中定义的每个sql的parameterType的类型相同

4) Mapper接口方法的输出参数类型和mapper.xml中定义的每个sql的resultType的类型相同

5.2.2 编写UserMapper接口

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-8xrR22Aa-1625401967506)(G:\PMY\专业资料\SSM\06-就业课(2.1)]-Mybatis\day02_Mybatis的dao层实现原理\笔记\img\图1.png)

5.2.3测试代理方式
@Test
public void testProxyDao() throws IOException {
    InputStream resourceAsStream = Resources.getResourceAsStream("SqlMapConfig.xml");
    SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(resourceAsStream);
    SqlSession sqlSession = sqlSessionFactory.openSession();
    //获得MyBatis框架生成的UserMapper接口的实现类
  UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
    User user = userMapper.findById(1);
    System.out.println(user);
    sqlSession.close();
}
5.3 知识小结

MyBatis的Dao层实现的两种方式:

手动对Dao进行实现:传统开发方式

代理方式对Dao进行实现:

 **UserMapper userMapper = sqlSession.getMapper(UserMapper.class);**

6.MyBatis映射文件深入

6.1 动态sql语句

6.1.1动态sql语句概述

Mybatis 的映射文件中,前面我们的 SQL 都是比较简单的,有些时候业务逻辑复杂时,我们的 SQL是动态变化的,此时在前面的学习中我们的 SQL 就不能满足要求了。

参考的官方文档,描述如下:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-pLQZUSF4-1625401967506)(G:\PMY\专业资料\SSM\06-就业课(2.1)]-Mybatis\day02_Mybatis的dao层实现原理\笔记\img\图片2.png)

6.1.2动态 SQL 之<if>

我们根据实体类的不同取值,使用不同的 SQL语句来进行查询。比如在 id如果不为空时可以根据id查询,如果username 不同空时还要加入用户名作为条件。这种情况在我们的多条件组合查询中经常会碰到。

<select id="findByCondition" parameterType="user" resultType="user">
    select * from User
    <where>
        <if test="id!=0">
            and id=#{id}
        </if>
        <if test="username!=null">
            and username=#{username}
        </if>
    </where>
</select>

当查询条件id和username都存在时,控制台打印的sql语句如下:

     … … …
     //获得MyBatis框架生成的UserMapper接口的实现类
  UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
    User condition = new User();
    condition.setId(1);
    condition.setUsername("lucy");
    User user = userMapper.findByCondition(condition);
    … … …

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-yohyOy5f-1625401967507)(G:\PMY\专业资料\SSM\06-就业课(2.1)]-Mybatis\day02_Mybatis的dao层实现原理\笔记\img\图片3.png)

当查询条件只有id存在时,控制台打印的sql语句如下:

 … … …
 //获得MyBatis框架生成的UserMapper接口的实现类
UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
User condition = new User();
condition.setId(1);
User user = userMapper.findByCondition(condition);
… … …

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-3ayOj7WH-1625401967507)(G:\PMY\专业资料\SSM\06-就业课(2.1)]-Mybatis\day02_Mybatis的dao层实现原理\笔记\img\图片4.png)

6.1.3 动态 SQL 之<foreach>

循环执行sql的拼接操作,例如:SELECT * FROM USER WHERE id IN (1,2,5)。

<select id="findByIds" parameterType="list" resultType="user">
   select * from User
   <where>
       <foreach collection="array" open="id in(" close=")" item="id" separator=",">
           #{id}
       </foreach>
   </where>
</select>

测试代码片段如下:

 … … …
 //获得MyBatis框架生成的UserMapper接口的实现类
UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
int[] ids = new int[]{2,5};
List<User> userList = userMapper.findByIds(ids);
System.out.println(userList);
… … …

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-PszW4JOt-1625401967508)(G:\PMY\专业资料\SSM\06-就业课(2.1)]-Mybatis\day02_Mybatis的dao层实现原理\笔记\img\图片5.png)

foreach标签的属性含义如下:

标签用于遍历集合,它的属性:

•collection:代表要遍历的集合元素,注意编写时不要写#{}

•open:代表语句的开始部分

•close:代表结束部分

•item:代表遍历集合的每个元素,生成的变量名

•sperator:代表分隔符

6.2 SQL片段抽取

Sql 中可将重复的 sql 提取出来,使用时用 include 引用即可,最终达到 sql 重用的目的

<!--抽取sql片段简化编写-->
<sql id="selectUser" select * from User</sql>
<select id="findById" parameterType="int" resultType="user">
    <include refid="selectUser"></include> where id=#{id}
</select>
<select id="findByIds" parameterType="list" resultType="user">
    <include refid="selectUser"></include>
    <where>
        <foreach collection="array" open="id in(" close=")" item="id" separator=",">
            #{id}
        </foreach>
    </where>
</select>

6.3 知识小结

MyBatis映射文件配置:

:查询
:插入

:修改

:删除

:where条件

:if判断

:循环

:sql片段抽取

7. MyBatis核心配置文件深入

7.1 typeHandlers标签

无论是 MyBatis 在预处理语句(PreparedStatement)中设置一个参数时,还是从结果集中取出一个值时, 都会用类型处理器将获取的值以合适的方式转换成 Java 类型。下表描述了一些默认的类型处理器(截取部分)。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-y11lxhJa-1625401967509)(G:\PMY\专业资料\SSM\06-就业课(2.1)]-Mybatis\day02_Mybatis的dao层实现原理\笔记\img\图片6.png)

你可以重写类型处理器或创建你自己的类型处理器来处理不支持的或非标准的类型。具体做法为:实现 org.apache.ibatis.type.TypeHandler 接口, 或继承一个很便利的类 org.apache.ibatis.type.BaseTypeHandler, 然后可以选择性地将它映射到一个JDBC类型。例如需求:一个Java中的Date数据类型,我想将之存到数据库的时候存成一个1970年至今的毫秒数,取出来时转换成java的Date,即java的Date与数据库的varchar毫秒值之间转换。

开发步骤:

①定义转换类继承类BaseTypeHandler

②覆盖4个未实现的方法,其中setNonNullParameter为java程序设置数据到数据库的回调方法,getNullableResult为查询时 mysql的字符串类型转换成 java的Type类型的方法

③在MyBatis核心配置文件中进行注册

测试转换是否正确

public class MyDateTypeHandler extends BaseTypeHandler<Date> {
    public void setNonNullParameter(PreparedStatement preparedStatement, int i, Date date, JdbcType type) {
        preparedStatement.setString(i,date.getTime()+"");
    }
    public Date getNullableResult(ResultSet resultSet, String s) throws SQLException {
        return new Date(resultSet.getLong(s));
    }
    public Date getNullableResult(ResultSet resultSet, int i) throws SQLException {
        return new Date(resultSet.getLong(i));
    }
    public Date getNullableResult(CallableStatement callableStatement, int i) throws SQLException {
        return callableStatement.getDate(i);
    }
}
<!--注册类型自定义转换器-->
<typeHandlers>
    <typeHandler handler="com.itheima.typeHandlers.MyDateTypeHandler"></typeHandler>
</typeHandlers>

测试添加操作:

user.setBirthday(new Date());
userMapper.add2(user);

数据库数据:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-O83QsI0v-1625401967509)(G:\PMY\专业资料\SSM\06-就业课(2.1)]-Mybatis\day02_Mybatis的dao层实现原理\笔记\img\图片7.png)

测试查询操作:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-U8gYwASj-1625401967510)(G:\PMY\专业资料\SSM\06-就业课(2.1)]-Mybatis\day02_Mybatis的dao层实现原理\笔记\img\图片8.png)

7.2 plugins标签

MyBatis可以使用第三方的插件来对功能进行扩展,分页助手PageHelper是将分页的复杂操作进行封装,使用简单的方式即可获得分页的相关数据

开发步骤:

①导入通用PageHelper的坐标

②在mybatis核心配置文件中配置PageHelper插件

③测试分页数据获取

①导入通用PageHelper坐标
<!-- 分页助手 -->
<dependency>
    <groupId>com.github.pagehelper</groupId>
    <artifactId>pagehelper</artifactId>
    <version>3.7.5</version>
</dependency>
<dependency>
    <groupId>com.github.jsqlparser</groupId>
    <artifactId>jsqlparser</artifactId>
    <version>0.9.1</version>
</dependency>

②在mybatis核心配置文件中配置PageHelper插件
<!-- 注意:分页助手的插件  配置在通用馆mapper之前 -->
<plugin interceptor="com.github.pagehelper.PageHelper">
    <!-- 指定方言 -->
    <property name="dialect" value="mysql"/>
</plugin>
③测试分页代码实现
@Test
public void testPageHelper(){
    //设置分页参数
    PageHelper.startPage(1,2);

    List<User> select = userMapper2.select(null);
    for(User user : select){
        System.out.println(user);
    }
}

获得分页相关的其他参数

//其他分页的数据
PageInfo<User> pageInfo = new PageInfo<User>(select);
System.out.println("总条数:"+pageInfo.getTotal());
System.out.println("总页数:"+pageInfo.getPages());
System.out.println("当前页:"+pageInfo.getPageNum());
System.out.println("每页显示长度:"+pageInfo.getPageSize());
System.out.println("是否第一页:"+pageInfo.isIsFirstPage());
System.out.println("是否最后一页:"+pageInfo.isIsLastPage());

7.3 知识小结

MyBatis核心配置文件常用标签:

1、properties标签:该标签可以加载外部的properties文件

2、typeAliases标签:设置类型别名

3、environments标签:数据源环境配置标签

4、typeHandlers标签:配置自定义类型处理器

5、plugins标签:配置MyBatis的插件

8.Mybatis的多表操作

一对一

 <resultMap id="orderMap" type="com.itheima.domain.Order"> 
     <result property="id" column="id"></result>
     <result property="ordertime" column="ordertime"></result>
     <result property="total" column="total"></result>
     
     <association property="user" javaType="com.itheima.domain.User">
         <result column="uid" property="user.id"></result>
         <result column="username" property="user.username"></result>
         <result column="password" property="user.password"></result> 
         <result column="birthday" property="user.birthday"></result>  
     </association>
</resultMap>
    
<select id="findAll" resultMap="orderMap">
    select * from orders o,user u where o.uid=u.id    
</select>

一对多

<resultMap id="userMap" type="com.itheima.domain.User"> 
    <result column="id" property="id"></result> 
    <result column="username" property="username"></result> 
    <result column="password" property="password"></result> 
    <result column="birthday" property="birthday"></result>
    
    <collection property="orderList" ofType="com.itheima.domain.Order">             			<result column="oid" property="id"></result>   
        <result column="ordertime" property="ordertime"></result>  
        <result column="total" property="total"></result> 
    </collection>
</resultMap>     
<select id="findAll" resultMap="userMap">       
    select *,o.id oid from user u left join orders o on u.id=o.uid  
</select> 

多对多

和一对多一样,只是sql语句的差别

知识小结:

一对一配置:使用+做配置

一对多配置:使用+做配置

多对多配置:使用+做配置

9.Mybatis注解开发

1.常用注解:

@Insert:实现新增

@Update:实现更新

@Delete:实现删除

@Select:实现查询

@Result:实现结果集封装

@Results:可以与@Result 一起使用,封装多个结果集

@One:实现一对一结果集封装

@Many:实现一对多结果集封装

修改MyBatis的核心配置文件,我们使用了注解替代的映射文件,所以我们只需要加载使用了注解的Mapper接口即可

< mappers >
   <!-- 扫描使用注解的类 -->
   <mapper class="com.itheima.mapper.UserMapper"></mapper>
</mappers>
< mappers >
   <!-- 扫描使用注解的类所在的包 -->
   <mapper name="com.itheima.mapper"></mapper>
</mappers>

注解:

注解说明
@Results代替的是标签该注解中可以使用单个@Result注解,也可以使用@Result集合。使用格式:@Results({@Result(),@Result()})或@Results(@Result())
@Resut代替了标签和标签 @Result中属性介绍: column:数据库的列名 property:需要装配的属性名 one:需要使用的@One 注解(@Result(one=@One)())) many:需要使用的@Many 注解(@Result(many=@many)()))
@One (一对一)代替了 标签,是多表查询的关键,在注解中用来指定子查询返回单一对象。 @One注解属性介绍: select: 指定用来多表查询的 sqlmapper 使用格式:@Result(column="",property="",one=@One(select=""))
@Many (多对一)代替了标签, 是是多表查询的关键,在注解中用来指定子查询返回对象集合。 使用格式:@Result(property="",column="",many=@Many(select=""))

一对一:

//查询订单顺便查询订单所属用户
@Select("select * from orders")
@Results({
        @Result(column = "id",property = "id"),
        @Result(column = "ordertime",property = "ordertime"),
        @Result(column = "total",property = "total"),
        @Result(column = "oid",property = "id"),
        @Result(
             property = "user",             //属性名
                javaType = User.class,      //属性类型
                column = "id",              //根据查询出来的结果集再去查询
                one =@One(select = "com.pmy.mapper.UserMapper.findById")
        ),
})
List<Order> findAll();

一对多:

//查询用户并查询他所属的订单
@Select("select *from user")
@Results({
        @Result(id = true,column = "id",property = "id"),
        @Result(column = "username",property = "username"),
        @Result(column = "password",property = "password"),
        @Result(column = "birthday",property = "birthday"),
        @Result(
                column = "id",
                javaType = List.class,
                many = @Many(select = "com.pmy.mapper.OrderMapper.findById")
        )
})
List<User> findUserAndOrderAll();

多对多:

//查询用户并查询用户的角色
@Select("select * from user")
@Results({
        @Result(id = true,column = "id",property = "id"),
        @Result(column = "username",property = "username"),
        @Result(column = "password",property = "password"),
        @Result(column = "birthday",property = "birthday"),
        @Result(
                property = "roleList",
                javaType = List.class,
                column = "id",
                many = @Many(select = "com.pmy.mapper.RoleMapper.findByUid")
        )
})
List<User> findUserAndRoleAll();

SpringBoot

SpringBoot

1. 环境搭建:

  1. 新建maven工程

  2. 导入依赖:

    <!--所有的springboot工程都必须继承spring-boot-starter-parent-->
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.3.9.RELEASE</version>
    </parent>
    
    <dependencies>
        <!--web功能的一个起步依赖-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
    </dependencies>
    
  3. 创建SpringBoot引导类

    //表明当前类是Spring Boot的引导类
    @SpringBootApplication
    public class MySpringBootApplication {
        public static void main(String[] args) {
       //run方法 表示运行SpringBoot的引导类 参数就说SpringBoot引导类的字节码对象
       //也就是标注了@SpringBootApplication注解的类的字节码对象
            SpringApplication.run(MySpringBootApplication.class);
        }
    }
    
  4. 编写controller层代码

2. SpringBoot的热部署

  1. 添加热部署的配置

    <!--热部署配置-->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-devtools</artifactId>
    </dependency>
    
  2. 设置IDEA开启热部署

    2.1 Settings-Complier-勾选BUild project automatically

    ​ 2.2 Shift+Ctrl+Alt+/ 选择Registry

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-t19xRTMC-1625401967510)(C:\Users\PMY\AppData\Roaming\Typora\typora-user-images\image-20210505173019564.png)]

//表示我下面所有的方法返回值为数据
@RestController

3.SpringBoot原理

4. IEAD快速创建SpringBoot工程

  1. 新建model的时候URL选阿里云的时候快一点

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Y6LEkz22-1625401967511)(C:\Users\PMY\AppData\Roaming\Typora\typora-user-images\image-20210428105706465.png)]

  1. 设置项目信息

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-kcdXglUT-1625401967511)(C:\Users\PMY\AppData\Roaming\Typora\typora-user-images\image-20210428105815076.png)]

  2. 设置项目依赖

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-I9d5bXfY-1625401967512)(C:\Users\PMY\AppData\Roaming\Typora\typora-user-images\image-20210428105834087.png)]

5. yml配置

配置文件加载顺序:

​ yml----->yaml----->properties

yml配置

​ 对象配置类似于Json,层级关系类似于Python

#普通数据的配置
name: zhangsan

#对象的配置
person:
  username: pmy
  password: 123
  age: 20

#行类对象配置
student: {username: pmy,password: 13,age: 21}

#数组或集合 (普通字符串)
city: 
  - beijing
  - tianjin
  - congqing
  - 上海
#数组行内方式:
city1: [beijing,tianjing,changde]

#数组内  对象
student1: 
  - name: tom
    age: 18
    gender: 0
  - name: pmy
    age: 20
    gender: 1
#数组内  对象
student2: [{name: pmy,age: 123},{name: pmy,age: 20}]

#
map配置
map:
  key1: v1
  key2: v2

6. 配置文件和属性的映射

5.1 普通属性使用@Value注解映射

​ 下面是多个注解, prefix是前缀

   @ConfigurationProperties(prefix = "person")

但是要映射的属性必须要有set、get方法

7. SpringBoot集成

Mybatis

  1. 添加Mybatis的起步依赖

  2. 添加数据库驱动坐标

  3. 添加数据库连接信息

    <!--导入mybatis的起步依赖  是mybaits提供的-->
    <dependency>
        <groupId>org.mybatis.spring.boot</groupId>
        <artifactId>mybatis-spring-boot-starter</artifactId>
        <version>1.1.1</version>
    </dependency>
    
    <!--Mysql驱动-->
    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
    </dependency>
    
  4. 创建表、实体Bean

  5. 创建Mapper的时候要么加上@Mapper注解,要么在入口类上加上@MapperScan(“com.pmy.mapper”) 注解包扫描

  6. 边界Controller类测试

#application.properties这个文件新建SpringBoot项目后会存在的
#数据库连接信息
spring.datasource.url=jdbc:mysql:///ssm?serverTimezone=GMT%2B8
spring.datasource.driver-class-name=com.mysql.jdbc.Driver
spring.datasource.username=root
spring.datasource.password=pmy0607+

#如果配置的是xml文件要加下面的配置
#配置mybatis的信息
#spring继承mybatis环境
#pojo别名扫描包
mybatis.type-aliases-package=com.pmy.domain
#加载Mybatis映射文件
mybatis.mapper-locations=classpath:mapper/*Mapper.xml

Junit

  1. 添加Junit的起步依赖

    SpringBoot工程默认导入了该依赖

    <!--Junit的起步依赖-->
    <dependency>    
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-test</artifactId>
    </dependency>
    
  2. 测试

    测试类上需要添加下两个注解

@RunWith(SpringRunner.class)//入口类的字节码对象@SpringBootTest(classes =SpringbootMybatisApplication.class)

Radis

  1. 添加Radis的起步依赖

    <!--配置使用redis启动器-->
    <dependency>    
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-data-redis</artifactId>
    </dependency>
    
  2. 配置Radis的连接信息

    #Redisspring.redis.host=localhostspring.redis.port=6379
    
  3. 测试

    import java.util.List;
    
    @RunWith(SpringRunner.class)
    @SpringBootTest(classes = SpringbootMybatisApplication.class)
    public class RedisTest {
        @Autowired
        private UserMapper mapper;
        @Autowired
        private RedisTemplate<String,String> redisTemplate ;
        @Test
        public void test() throws JsonProcessingException {
            // 1 先从radis中获得数据  json字符串
            String personListJson = redisTemplate.boundValueOps("user.findAll").get();
            // 2 判断radis中是否存在数据
            if (null == personListJson){
                // 2.1 缓存没有,则从数据库查数据
                List<Person> all = mapper.findAll();
    
                // 2.2 存到radis里面
                personListJson = new ObjectMapper().writeValueAsString(all);
                redisTemplate.boundValueOps("user.findAll").set(personListJson);
                System.out.println("数据库中拿数据");
            }else{
                System.out.println("缓存中拿数据");
            }
            
            // 3 输出数据
            System.out.println(personListJson);
        }
    }
    

y1: [beijing,tianjing,changde]

#数组内 对象
student1:

  • name: tom
    age: 18
    gender: 0
  • name: pmy
    age: 20
    gender: 1
    #数组内 对象
    student2: [{name: pmy,age: 123},{name: pmy,age: 20}]

map配置
map:
key1: v1
key2: v2




## 6. 配置文件和属性的映射

   5.1 普通属性使用@Value注解映射

​	下面是多个注解, prefix是前缀 

	   @ConfigurationProperties(prefix = "person")

 但是要映射的属性必须要有set、get方法



## 7. SpringBoot集成

### Mybatis

1. 添加Mybatis的起步依赖

2. 添加数据库驱动坐标

3. 添加数据库连接信息

   ```xml
   <!--导入mybatis的起步依赖  是mybaits提供的-->
   <dependency>
       <groupId>org.mybatis.spring.boot</groupId>
       <artifactId>mybatis-spring-boot-starter</artifactId>
       <version>1.1.1</version>
   </dependency>
   
   <!--Mysql驱动-->
   <dependency>
       <groupId>mysql</groupId>
       <artifactId>mysql-connector-java</artifactId>
   </dependency>
  1. 创建表、实体Bean

  2. 创建Mapper的时候要么加上@Mapper注解,要么在入口类上加上@MapperScan(“com.pmy.mapper”) 注解包扫描

  3. 边界Controller类测试

#application.properties这个文件新建SpringBoot项目后会存在的
#数据库连接信息
spring.datasource.url=jdbc:mysql:///ssm?serverTimezone=GMT%2B8
spring.datasource.driver-class-name=com.mysql.jdbc.Driver
spring.datasource.username=root
spring.datasource.password=pmy0607+

#如果配置的是xml文件要加下面的配置
#配置mybatis的信息
#spring继承mybatis环境
#pojo别名扫描包
mybatis.type-aliases-package=com.pmy.domain
#加载Mybatis映射文件
mybatis.mapper-locations=classpath:mapper/*Mapper.xml

Junit

  1. 添加Junit的起步依赖

    SpringBoot工程默认导入了该依赖

    <!--Junit的起步依赖-->
    <dependency>    
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-test</artifactId>
    </dependency>
    
  2. 测试

    测试类上需要添加下两个注解

@RunWith(SpringRunner.class)//入口类的字节码对象@SpringBootTest(classes =SpringbootMybatisApplication.class)

Radis

  1. 添加Radis的起步依赖

    <!--配置使用redis启动器-->
    <dependency>    
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-data-redis</artifactId>
    </dependency>
    
  2. 配置Radis的连接信息

    #Redisspring.redis.host=localhostspring.redis.port=6379
    
  3. 测试

    import java.util.List;
    
    @RunWith(SpringRunner.class)
    @SpringBootTest(classes = SpringbootMybatisApplication.class)
    public class RedisTest {
        @Autowired
        private UserMapper mapper;
        @Autowired
        private RedisTemplate<String,String> redisTemplate ;
        @Test
        public void test() throws JsonProcessingException {
            // 1 先从radis中获得数据  json字符串
            String personListJson = redisTemplate.boundValueOps("user.findAll").get();
            // 2 判断radis中是否存在数据
            if (null == personListJson){
                // 2.1 缓存没有,则从数据库查数据
                List<Person> all = mapper.findAll();
    
                // 2.2 存到radis里面
                personListJson = new ObjectMapper().writeValueAsString(all);
                redisTemplate.boundValueOps("user.findAll").set(personListJson);
                System.out.println("数据库中拿数据");
            }else{
                System.out.println("缓存中拿数据");
            }
            
            // 3 输出数据
            System.out.println(personListJson);
        }
    }
    
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
SSM(Spring + SpringMVC + MyBatis)框架中使用Uploadify文件上传,首先需要在前端页面引入Uploadify插件,并编写相应的HTML和JavaScript代码来实现文件上传功能。在SpringMVC中需要编写文件上传的Controller来处理上传的文件,并在Controller中使用MultipartFile对象来接收文件数据,然后将文件保存到服务器的指定目录中。同时,还需要在Spring的配置文件中配置文件上传相关的Bean和参数,以确保正常的文件上传功能。 在处理文件上传的Controller中,可以使用MultipartFile对象的方法来获取文件的原始名称、大小、类型等信息,并对文件进行合法性校验,例如限制文件类型、大小等。接着将文件保存到服务器的指定目录中,可以使用File的相关API来实现文件的保存和写入操作。 在MyBatis中,若需要将文件信息保存到数据库中,需要创建相应的实体类来映射文件的信息,并编写相应的Mapper接口和SQL语句来实现文件信息的插入、查询等操作。 综上所述,使用Uploadify文件上传需要在前端引入插件并编写相应的HTML、JavaScript代码;在SpringMVC中编写文件上传的Controller来处理文件上传,并在Spring的配置文件中配置文件上传相关的Bean和参数;在MyBatis中编写相应的实体类、Mapper接口和SQL语句来实现文件信息的插入、查询等操作。通过以上步骤,就可以在SSM框架中成功实现Uploadify文件上传的功能。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值