SSM框架教程

Spring

Spring简介

Spring是什么

Spring是分层的Java SE/EE应用full-stack轻量级开源框架,以IoC(Inverse Of Control:反转控制)和AOP(Aspect Oriented Programming:面向切面编程)为内核

提供了展现层SpringMVC和持久层SpringJDBCTemplate以及业务层事务管理等众多的企业级应用技术,还能整合开源世界众多著名的第三方框架和类库,逐渐成为使用最多的Java EE企业级应用开源框架。

Spring发展历程

  • 1997年,IBM提出了EJB的思想
  • 1998年,SUN制定了开发标准规范EJB 1.0
  • 1999年,EJB 1.1发布
  • 2001年,EJB 2.0发布
  • 2003年,EJB 2.1发布
  • 2006年,EJB 3.0发布

img

Rod Johnson(Spring之父)

Expert One-to-One J2EE Design and Development(2002)

阐述了J2EE使用EJB开发设计的优点及解决方案

Expert One-to-One J2EE Development without EJB(2004)

阐述了J2EE开发不适用EJB的解决方式(Spring雏形)

2017年9月份发布了Spring的最新版本Spring 5.0通用版(GA)

Spring的优势

  1. 方便解耦,简化开发

    通过Spring提供的IoC容器,可以将对象间的依赖关系交由Spring进行控制,避免硬编码所造成的过度耦合。

    用户也不必再为单例模式类、属性文件解析等这些很底层的需求编写代码,可以更专注于上层的应用。

  2. AOP编程的支持

    通过Spring的AOP功能,方便进行面向切面编程,许多不容易传统OOP实现的功能可以通过AOP轻松实现。

  3. 声明式事务的支持

    可以将我们从单调烦闷的事务管理代码中解脱出来,通过声明式方式灵活的进行事务管理,提高开发效率和质量。

  4. 方便程序的测试

    可以用非容器依赖的编程方式进行几乎所有的测试工作,测试不再是昂贵的操作,而是随手可做的事情。

  5. 方便集成各种优秀框架

    Spring对各种优秀框架(Struts、Hibemate、Hessian、Quartz等)的支持。

  6. 降低Java EE API的使用难度

    Spring对JavaEE API(如JDBC、JavaMail、远程调度等)进行了薄薄的封装层,使这些API的使用难度大为降低。

  7. Java源码是经典学习范例

    Spring的源代码设计精妙、结构清晰、匠心独有,处处体现着大师对Java设计模式灵活运用以及对Java技术的高深造诣。它的源码无疑是Java技术的最佳实践的范例。

    Spring的体系结构

img

Spring快速入门

Spring程序开发步骤

  1. 导入Spring开发的基本包坐标
  2. 编写Dao接口和实现类
  3. 创建Spring核心配置文件
  4. 在Spring配置文件中配置UserDaoImpl
  5. 使用Spring的API获得Bean实例

Maven依赖

<dependency>
    <groupId>junit</groupId>
    <artifactId>junit</artifactId>
    <version>4.12</version>
    <scope>test</scope>
</dependency>
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-context</artifactId>
    <version>5.2.9.RELEASE</version>
</dependency>

项目结构

image

UserDao

package org.example.dao;

/**
 * @author admin
 */
public interface UserDao {
    /**
     * 测试方法
     */
    void save();
}

UserDaoImpl

package org.example.dao.impl;

import org.example.dao.UserDao;

/**
 * @author admin
 */
public class UserDaoImpl implements UserDao {
    public void save() {
        System.out.println("save running...");
    }
}

applicationContext.xml

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

    <bean id="userDao" class="org.example.dao.impl.UserDaoImpl"/>

</beans>

测试

package org.example.dao;

import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class UserDaoTest {

    @Test
    public void testSave(){
        ApplicationContext context=new ClassPathXmlApplicationContext("applicationContext.xml");
        UserDao userDao = context.getBean("userDao", UserDao.class);
        userDao.save();

    }
}

结果

image

知识要点

Spring开发步骤

  1. 导入坐标
  2. 创建Bean
  3. 创建applicationContext.xml
  4. 在配置文件中进行配置
  5. 创建ApplicationContext对象getBean

Spring配置文件

Bean标签基本配置

用于配置对象交由Spring来创建

默认情况下它调用的是类中无参构造函数,如果没有无参构造函数则不能创建成功

基本属性:

  • id:Bean实例在Spring容器中的唯一标识
  • class:Bean的全限定类名

Bean标签范围配置

scope:指对象的作用范围,取值如下:

取值范围 说明
singleton 默认值,单例的
prototype 多例的
request web项目中,spring创建一个bean对象,将对象存入到request域中
session web项目中,spring创建一个bean对象,将对象存入到session域中
global session web项目中,应用在Portlet环境,如果没有Portlet环境那么globalSession相当于session

单例

applicationContext.xml

<bean id="userDao" class="org.example.dao.impl.UserDaoImpl" scope="singleton"/>

测试

@Test
public void testScope_singleton(){
    ApplicationContext context=new ClassPathXmlApplicationContext("applicationContext.xml");
    UserDao userDao1 = context.getBean("userDao", UserDao.class);
    UserDao userDao2 = context.getBean("userDao", UserDao.class);
    System.out.println(userDao1);
    System.out.println(userDao2);
    System.out.println(userDao1==userDao2);
}

结果

image

多例

applicationContext.xml

<bean id="userDao" class="org.example.dao.impl.UserDaoImpl" scope="prototype"/>

测试

@Test
public void testScope_singleton(){
    ApplicationContext context=new ClassPathXmlApplicationContext("applicationContext.xml");
    UserDao userDao1 = context.getBean("userDao", UserDao.class);
    UserDao userDao2 = context.getBean("userDao", UserDao.class);
    System.out.println(userDao1);
    System.out.println(userDao2);
    System.out.println(userDao1==userDao2);
}

结果

image

1)当scope的取值为singleton时

bean的实例化个数:1个

bean的实例化时机:当spring核心配置文件被加载时,实例化配置的bean实例

bean的生命周期:

  • 对象创建:当应用加载,创建容器时,对象就被创建了
  • 对象运行:只要容器在,对象一直活着
  • 对象销毁:当应用卸载,销毁容器时,对象就被销毁了

2)当scope的取值为prototype时

bean的实例化个数:多个

bean的实例化时机:当调用getBean()方法时实例化bean

  • 对象创建:当使用对象时,创建新的对象实例
  • 对象运行:只要对象在使用中,就一直活着
  • 对象销毁:当对象长时间不用时,被Java的垃圾回收器回收了

Bean生命周期配置

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

UserDaoImpl

package org.example.dao.impl;

import org.example.dao.UserDao;

/**
 * @author admin
 */
public class UserDaoImpl implements UserDao {

    public void init(){
        System.out.println("初始化方法...");
    }

    public void destroy(){
        System.out.println("销毁方法...");
    }

    public UserDaoImpl() {
        System.out.println("UserDaoImpl创建了...");
    }

    public void save() {
        System.out.println("save running...");
    }
}

applicationContext.xml

<bean id="userDao" class="org.example.dao.impl.UserDaoImpl" init-method="init" destroy-method="destroy"/>

测试

@Test
public void testInitAndDestroyMethod(){
    ClassPathXmlApplicationContext context=new ClassPathXmlApplicationContext("applicationContext.xml");
    UserDao userDao = context.getBean("userDao", UserDao.class);
    userDao.save();
    context.close();
}

结果

image

Bean实例化三种方式

  • 无参构造方法实例化
  • 工厂静态方法实例化
  • 工厂实例方法实例化

静态工厂

StaticFactory

package org.example.factory;

import org.example.dao.UserDao;
import org.example.dao.impl.UserDaoImpl;

/**
 * @author admin
 */
public class StaticFactory {

    public static UserDao userDao(){
        return new UserDaoImpl();
    }
}

applicationContex.xml

<bean id="userDao" class="org.example.factory.StaticFactory" factory-method="userDao"/>

测试

@Test
public void testGetUserDaoByStaticFactory(){
    ApplicationContext context=new ClassPathXmlApplicationContext("applicationContext.xml");
    UserDao userDao = context.getBean("userDao", UserDao.class);
    userDao.save();
}

结果

image

动态工厂

DynamicFactory

package org.example.factory;

import org.example.dao.UserDao;
import org.example.dao.impl.UserDaoImpl;

/**
 * @author admin
 */
public class DynamicFactory {

    public UserDao userDao(){
        return new UserDaoImpl();
    }
}

applicationContext.xml

<!--动态工厂实例化-->
<bean id="factory" class="org.example.factory.DynamicFactory"/>
<bean id="userDao" factory-bean="factory" factory-method="userDao"/>

测试

@Test
public void testGetUserDaoByDynamicFactory(){
    ApplicationContext context=new ClassPathXmlApplicationContext("applicationContext.xml");
    UserDao userDao = context.getBean("userDao", UserDao.class);
    userDao.save();
}

结果

image

Bean的依赖注入

依赖注入(Dependency Injection):它是Spring框架核心IOC的具体实现

在编写程序时,通过控制反转,把对象的创建交给了Spring,但是代码中不可能出现没有依赖的情况。

IOC解耦只是降低他们的依赖关系,但不会消除。例如:业务层仍会调用持久层的方法。

那这种业务层和持久层的依赖关系,在使用Spring之后,就让Spring来维护了。

简单的说,就是坐等框架把持久层对象传入业务层,而不用我们自己去获取。

怎么将UserDao注入到UserService内部呢?

  • 构造方法
  • set方法

1)set方法

UserService

package org.example.service;

/**
 * @author admin
 */
public interface UserService {

    /**
     * 测试方法
     */
    void save();
}

UserServiceImpl

package org.example.service.impl;

import org.example.dao.UserDao;
import org.example.service.UserService;

/**
 * @author admin
 */
public class UserServiceImpl implements UserService {

    private UserDao userDao;

    public void setUserDao(UserDao userDao) {
        this.userDao = userDao;
    }

    public void save() {
        userDao.save();
    }
}

applicationContext.xml

<!--依赖注入-->
<bean id="userDao" class="org.example.dao.impl.UserDaoImpl"/>
<bean id="userService" class="org.example.service.impl.UserServiceImpl">
    <property name="userDao" ref="userDao"/>
</bean>

测试

@Test
public void testSave(){
    ApplicationContext context=new ClassPathXmlApplicationContext("applicationContext.xml");
    UserService userService = context.getBean("userService", UserService.class);
    userService.save();
}

结果

image

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

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

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

其次,需要修改注入方式

<!--p命名空间引入-->
<bean id="userService" class="org.example.service.impl.UserServiceImpl" p:userDao-ref="userDao"/>

2)构造方法注入

UserServiceImpl

package org.example.service.impl;

import org.example.dao.UserDao;
import org.example.service.UserService;

/**
 * @author admin
 */
public class UserServiceImpl implements UserService {

    private UserDao userDao;

    public UserServiceImpl() {
    }

    public UserServiceImpl(UserDao userDao) {
        this.userDao = userDao;
    }

    public void save() {
        userDao.save();
    }
}

applicationContext.xml

<!--构造方法注入-->
<bean id="userService" class="org.example.service.impl.UserServiceImpl">
    <constructor-arg name="userDao" ref="userDao"/>
</bean>

Bean的依赖注入的数据类型

上面的操作都是注入的引用Bean,除了对象的引用可以注入,普通数据类型,集合等都可以在容器中进行注入。

注入数据的三种数据类型

  • 普通数据类型
  • 引用数据类型
  • 集合数据类型

普通类型注入

先准备一个User的实体类

package org.example.domain;

/**
 * @author admin
 */
public class User {
    private String name;
    private int age;

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

    public void setName(String name) {
        this.name = name;
    }

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

applicationContext.xml

<!--普通类型注入-->
<bean id="user" class="org.example.domain.User">
    <property name="name" value="张三"/>
    <property name="age" value="21"/>
</bean>

测试

@Test
public void testUser(){
    ApplicationContext context=new ClassPathXmlApplicationContext("applicationContext.xml");
    User user = context.getBean("user", User.class);
    System.out.println(user);
}

引用注入和集合注入

先准备一个Course实体类

package org.example.domain;

/**
 * @author admin
 */
public class Course {
    private int id;
    private String courseName;

    @Override
    public String toString() {
        return "Course{" +
                "id=" + id +
                ", courseName='" + courseName + '\'' +
                '}';
    }

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

    public void setCourseName(String courseName) {
        this.courseName = courseName;
    }
}

User

package org.example.domain;

import java.util.List;
import java.util.Map;
import java.util.Properties;

/**
 * @author admin
 */
public class User {
    private String name;
    private int age;
    private List<String> hobbys;
    private Map<String,Course> courses;
    private Properties properties;

    @Override
    public String toString() {
        return "User{" +
                "name='" + name + '\'' +
                ", age=" + age +
                ", hobbys=" + hobbys +
                ", courses=" + courses +
                ", properties=" + properties +
                '}';
    }

    public void setHobbys(List<String> hobbys) {
        this.hobbys = hobbys;
    }

    public void setCourses(Map<String, Course> courses) {
        this.courses = courses;
    }

    public void setProperties(Properties properties) {
        this.properties = properties;
    }

    public void setName(String name) {
        this.name = name;
    }

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

applicationContext.xml

<bean id="math" class="org.example.domain.Course" p:id="1" p:courseName="数学"/>
<bean id="language" class="org.example.domain.Course" p:id="2" p:courseName="语文"/>
<bean id="english" class="org.example.domain.Course" p:id="3" p:courseName="英语"/>
<bean id="user" class="org.example.domain.User">
    <!--普通类型注入-->
    <property name="name" value="张三"/>
    <property name="age" value="21"/>
    <!--集合注入-->
    <property name="hobbys">
        <list>
            <value>睡觉</value>
            <value>学习</value>
            <value>游戏</value>
            <value>计算机</value>
        </list>
    </property>
    <property name="courses">
        <map>
            <entry key="数学" value-ref="math"/>
            <entry key="语文" value-ref="language"/>
            <entry key="英语" value-ref="english"/>
        </map>
    </property>
    <property name="properties">
        <props>
            <prop key="1">嘻嘻</prop>
            <prop key="2">哈哈</prop>
            <prop key="3">桀桀</prop>
        </props>
    </property>
</bean>

测试

@Test
public void testUser(){
    ApplicationContext context=new ClassPathXmlApplicationContext("applicationContext.xml");
    User user = context.getBean("user", User.class);
    System.out.println(user);
}

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

实际开发中,Spring的配置内容非常多,这就导致Spring的配置很繁杂且体积很大,所以,可以将部分配置拆解到其他配置文件,而在Spring主配置文件通过imiport标签进行加载

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

项目结构

image

applicationContext.xml

<!--引入其他配置文件-->
<import resource="applicationContext-user.xml"/>
<import resource="applicationContext-product.xml"/>

Spring相关API

ApplicationContext的实现类

  1. ClassPathXmlApplicationContext

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

  2. FileSystemXmlApplicationContext

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

  3. AnnotationConfigApplicationContext

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

Spring配置数据源

数据源(连接池)的作用

  • 数据源(连接池)是提高程序性能而出现的
  • 事先实例化数据源,初始化部分连接资源
  • 使用连接资源时从数据源中获取
  • 使用完毕后将连接资源归还给数据源

常见的数据源(连接池):DBCP、C3P0、BoneCP、Druid等

数据源的开发步骤

  1. 导入数据源的坐标和数据库驱动坐标
  2. 创建数据源对象
  3. 设置数据源的基本连接数据
  4. 使用数据源获取连接资源和归还连接资源

db.properties

jdbc.driver=com.mysql.cj.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/test?characterEncoding=utf8&useSSL=true&serverTimezone=UTC
jdbc.username=root
jdbc.password=123456

测试

/**
 * 通过读取配置文件手动创建C3P0数据源
 * @throws PropertyVetoException
 * @throws SQLException
 */
@Test
public void testC3P0ByHand_properties() throws PropertyVetoException, SQLException {
    //读取配置文件
    ResourceBundle db = ResourceBundle.getBundle("db");
    String driver = db.getString("jdbc.driver");
    String url = db.getString("jdbc.url");
    String username = db.getString("jdbc.username");
    String password = db.getString("jdbc.password");
    ComboPooledDataSource dataSource = new ComboPooledDataSource();
    dataSource.setDriverClass(driver);
    dataSource.setJdbcUrl(url);
    dataSource.setUser(username);
    dataSource.setPassword(password);
    Connection connection = dataSource.getConnection();
    System.out.println(connection);
    connection.close();
}

/**
 * 手动创建Druid数据源
 * @throws SQLException
 */
@Test
public void testDruidByHand() throws SQLException {
    DruidDataSource dataSource = new DruidDataSource();
    dataSource.setDriverClassName("com.mysql.cj.jdbc.Driver");
    dataSource.setUrl("jdbc:mysql://localhost:3306/test?characterEncoding=utf8&useSSL=true&serverTimezone=UTC");
    dataSource.setUsername("root");
    dataSource.setPassword("123456");
    DruidPooledConnection connection = dataSource.getConnection();
    System.out.println(connection);
    connection.close();
}

/**
 * 手动创建C3P0数据源
 * @throws PropertyVetoException
 * @throws SQLException
 */
@Test
public void testC3P0ByHand() throws PropertyVetoException, SQLException {
    ComboPooledDataSource dataSource = new ComboPooledDataSource();
    dataSource.setDriverClass("com.mysql.cj.jdbc.Driver");
    dataSource.setJdbcUrl("jdbc:mysql://localhost:3306/test?characterEncoding=utf8&useSSL=true&serverTimezone=UTC");
    dataSource.setUser("root");
    dataSource.setPassword("123456");
    Connection connection = dataSource.getConnection();
    System.out.println(connection);
    connection.close();
}

Spring配置数据源

applicationContext.xml

<!--加载外部properties配置文件-->
<context:property-placeholder location="db.properties"/>

<!--配置Druid数据源-->
<bean id="druidDataSource" class="com.alibaba.druid.pool.DruidDataSource">
    <property name="driverClassName" value="${jdbc.driver}"/>
    <property name="url" value="${jdbc.url}"/>
    <property name="username" value="${jdbc.username}"/>
    <property name="password" value="${jdbc.password}"/>
</bean>

<!--配置C3P0数据源-->
<bean id="c3p0DataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
    <property name="driverClass" value="${jdbc.driver}"/>
    <property name="jdbcUrl" value="${jdbc.url}"/>
    <property name="user" value="${jdbc.username}"/>
    <property name="password" value="${jdbc.password}"/>
</bean>

测试

@Test
public void testGetDatabaseByIoc() throws SQLException {
    ApplicationContext context=new ClassPathXmlApplicationContext("applicationContext.xml");
    DataSource dataSource = context.getBean("c3p0DataSource", DataSource.class);
    Connection connection = dataSource.getConnection();
    System.out.println(connection);
    connection.close();
}

Spring注解开发

Spring原始注解

spring是轻代码而重配置的框架,配置比较繁重,影响开发效率,所以注解开发是一种趋势,注解代替xml配置文件可以简化配置,提高开发效率。

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的销毁方法

UserDaoimpl

package com.example.dao.impl;

import com.example.dao.UserDao;
import org.springframework.stereotype.Component;

/**
 * @author admin
 */
@Component("userDao")
public class UserDaoImpl implements UserDao {
    public void save() {
        System.out.println("save running...");
    }
}

UserServiceImpl

package com.example.service.impl;

import com.example.dao.UserDao;
import com.example.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Component;

/**
 * @author admin
 */
@Component("userService")
public class UserServiceImpl implements UserService {

    @Autowired
    @Qualifier("userDao")
    private UserDao userDao;

    public void setUserDao(UserDao userDao) {
        this.userDao = userDao;
    }

    public void save() {
        userDao.save();
    }
}

applicationContext.xml

<!--配置组件扫描-->
<context:component-scan base-package="com.example"/>

测试

@Test
public void testSave(){
    ApplicationContext context=new ClassPathXmlApplicationContext("applicationContext.xml");
    UserService userService = context.getBean("userService", UserService.class);
    userService.save();
}

Spring新注解

使用上面的注解还不能全部替代xml配置文件,还需要使用注解替代的配置如下:

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

DataSourceConfiguration

package com.example.config;

import com.mchange.v2.c3p0.ComboPooledDataSource;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.PropertySource;

import javax.sql.DataSource;
import java.beans.PropertyVetoException;

/**
 * @PropertySource 加载配置文件
 * @author admin
 */
@Configuration
@PropertySource("classpath:db.properties")
public class DataSourceConfiguration {

    @Value("${jdbc.driver}")
    private String driver;
    @Value("${jdbc.url}")
    private String url;
    @Value("${jdbc.username}")
    private String username;
    @Value("${jdbc.password}")
    private String password;

    @Bean
    public DataSource dataSource() throws PropertyVetoException {
        ComboPooledDataSource dataSource = new ComboPooledDataSource();
        dataSource.setDriverClass(driver);
        dataSource.setJdbcUrl(url);
        dataSource.setUser(username);
        dataSource.setPassword(password);
        return dataSource;
    }
}

SpringConfiguration

package com.example.config;

import com.mchange.v2.c3p0.ComboPooledDataSource;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.*;

import javax.sql.DataSource;
import java.beans.PropertyVetoException;

/**
 * @Import 导入其他配置类
 * @ComponentScan 配置组件扫描
 * @Configuration 表明该类是Spring核心配置类
 * @author admin
 */
@Configuration
@ComponentScan("com.example")
@Import(DataSourceConfiguration.class)
public class SpringConfiguration {

}

测试

@Test
public void testConfiguration() throws SQLException {
    ApplicationContext context=new AnnotationConfigApplicationContext(SpringConfiguration.class);
    DataSource dataSource = context.getBean(DataSource.class);
    Connection connection = dataSource.getConnection();
    System.out.println(connection);
    connection.close();
}

Spring集成Junit

原始Junit测试Spring的问题

在测试类中,每个测试方法都有以下两行代码:

ApplicationContext context=new ClassPathXmlApplicationContext("applicationContext.xml");
UserService userService=context.getBean("userService",UserService.class);

这两行代码的作用是获取容器,如果不写的话,直接会提示空指针异常。所以又不能轻易删掉。

上述问题解决思路

  • 让SpringJunit负责创建Spring容器,但是需要将配置文件的名称告诉它
  • 将需要进行测试Bean直接在测试类中进行注入

Spring集成Junit步骤

  1. 导入Spring集成Junit的坐标
  2. 使用@Runwith注解替换原来的运行期
  3. 使用@ContextConfiguration指定配置文件或配置类
  4. 使用@Autowired注入需要测试的对象
  5. 创建测试方法进行测试

pom.xml

<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-test</artifactId>
    <version>5.2.9.RELEASE</version>
</dependency>

测试

package com.example.service;

import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:applicationContext.xml")
public class SpringJunitTest {

    @Autowired
    private UserService userService;

    @Test
    public void testSave(){
        userService.save();
    }
}

Spring集成web环境

ApplicationContext应用上下文获取方式

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

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

ContextLoaderListener

package com.example.listener;

import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

import javax.servlet.ServletContext;
import javax.servlet.ServletContextEvent;
import javax.servlet.ServletContextListener;

/**
 * @author admin
 */
public class ContextLoaderListener implements ServletContextListener {
    public void contextInitialized(ServletContextEvent servletContextEvent) {
        //将Spring的应用上下文对象存储到ServletContext域中
        ServletContext servletContext = servletContextEvent.getServletContext();
        //读取web.xml中的全局参数
        String contextConfigLocation = servletContext.getInitParameter("contextConfigLocation");
        ApplicationContext context=new ClassPathXmlApplicationContext(contextConfigLocation);
        servletContext.setAttribute("context",context);
        System.out.println("context存储完成...");
    }

    public void contextDestroyed(ServletContextEvent servletContextEvent) {

    }
}

web.xml

<!--全局初始化参数-->
<context-param>
    <param-name>contextConfigLocation</param-name>
    <param-value>classpath:applicationContext.xml</param-value>
</context-param>

<!--上下文监听器-->
<listener>
    <listener-class>com.example.listener.ContextLoaderListener</listener-class>
</listener>

<servlet>
    <servlet-name>userService</servlet-name>
    <servlet-class>com.example.controller.UserServlet</servlet-class>
</servlet>
<servlet-mapping>
    <servlet-name>userService</servlet-name>
    <url-pattern>/userServlet</url-pattern>
</servlet-mapping>

WebApplicationContextUtil

package com.example.listener;

import org.springframework.context.ApplicationContext;

import javax.servlet.ServletContext;

/**
 * @author admin
 */
public class WebApplicationContextUtil {

    public static ApplicationContext getWebApplicationContext(ServletContext servletContext){
        return (ApplicationContext) servletContext.getAttribute("context");
    }
}

UserServlet

package com.example.controller;

import com.example.listener.WebApplicationContextUtil;
import com.example.service.UserService;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

/**
 * @author admin
 */
public class UserServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        ApplicationContext context = WebApplicationContextUtil.getWebApplicationContext(req.getServletContext());
        UserService userService = context.getBean("userService", UserService.class);
        userService.save();
    }
}

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

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

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

  1. 在web.xml中配置ContextLoaderListener监听器(导入spring-web坐标)
  2. 使用WebApplicationContextUtils获得应用上下文对象ApplicationContext

pom.xml

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

web.xml

<listener>
    <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>

UserServlet

package com.example.controller;

import com.example.listener.WebApplicationContextUtil;
import com.example.service.UserService;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.web.context.WebApplicationContext;
import org.springframework.web.context.support.WebApplicationContextUtils;

import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

/**
 * @author admin
 */
public class UserServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        /*ApplicationContext context = WebApplicationContextUtil.getWebApplicationContext(req.getServletContext());
        UserService userService = context.getBean("userService", UserService.class);
        userService.save();*/
        ServletContext servletContext = req.getServletContext();
        ApplicationContext context = WebApplicationContextUtils.getWebApplicationContext(servletContext);
        UserService userService = context.getBean("userService", UserService.class);
        userService.save();
    }
}

SpringMVC简介

SpringMVC概述

SpringMVC是一种基于Java的实现MVC设计模型的请求驱动类型的轻量级web框架,属于SpringFrameWork的后续产品,已经融合在Spring Web Flow中。

SpringMVC已经成为目前最主流的MVC框架之一,并且随着Spring3.0的发布,全面超越了Struts2,成为最优秀的MVC框架。它通过一套注解,让一个简单的Java类成为处理请求的控制器,而无须实现任何接口。同时它还支持RESTful编程风格的请求。

SpringMVC快速入门

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

开发步骤:

  1. 导入SpringMVC相关坐标
  2. 配置SpringMVC核心控制器DispatcherServlet
  3. 创建Controller类和视图页面
  4. 使用注解Controller类中业务方法的映射地址
  5. 配置SpringMVC核心文件spring-mvc.xml
  6. 客户端发起请求测试

pom.xml

<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-webmvc</artifactId>
    <version>5.2.9.RELEASE</version>
</dependency>

web.xml

<!--配置SpringMVC前端控制器-->
<servlet>
    <servlet-name>springmvc</servlet-name>
    <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
    <init-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>classpath:spring-mvc.xml</param-value>
    </init-param>
    <load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
    <servlet-name>springmvc</servlet-name>
    <url-pattern>/</url-pattern>
</servlet-mapping>

spring-mvc.xml

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

    <!--controller包下的组件扫描-->
    <context:component-scan base-package="com.example.controller"/>

</beans>

UserController

package com.example.controller;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;

/**
 * @author admin
 */
@Controller
public class UserController {

    @RequestMapping("/quick")
    public String quick(){
        return "success.jsp";
    }
}

SpringMVC组件解析

SpringMVC执行流程

image

  1. 用户发送请求至前端控制器DispatcherServlet
  2. DispatcherServlet收到请求调用HandlerMapping处理器映射器
  3. 处理器映射器找到具体的处理器(可以根据xml配置、注解进行查找),生成处理器对象及处理器拦截器(如果有则生成)一并返回给DispatcherServlet
  4. DispatcherServlet调用HandlerAdapter处理器适配器
  5. HandlerAdapter经过适配调用具体的处理器(Controller,也叫后端控制器)
  6. Controller执行完成返回ModelAndView
  7. HandlerAdapter将controller执行结果ModelAndView返回给DispatcherServlet
  8. DispatcherServlet将ModelAndView传给ViewResolver视图解析器
  9. ViewResolver解析后返回具体View
  10. DispatcherServlet根据View进行渲染视图(即将模型数据填充至视图中)。DispatcherServlet响应用户

SpringMVC注解解析

@RequestMapping

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

位置:

  • 类上,请求URL的第一级访问目录。此处不写的话,就相当于应用的根目录
  • 方法上,请求URL的第二级访问目录,与类上的使用@RequestMapping标注的一级目录一起组成访问虚拟路径

属性:

  • value:用于指定请求的URL,它和path属性的作用是一样的
  • method:用于指定请求的方式
  • params:用于指定限制请求参数的条件。它支持简单的表达式。要求请求参数的key和value必须和配置的一模一样

例如:

  • params={"accountName"},表示请求参数必须有accountName
  • params={"money!100"},表示请求参数中money不能是100

mvc命名空间引入

命名空间:

xmlns:mvc="http://www.springframework.org/schema/mvc"
xmlns:context="http://www.springframework.org/schema/context"

约束地址:

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.example.controller"/>进行组件扫描

内部资源视图解析器

spring-mvc.xml

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

UserController

package com.example.controller;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;

/**
 * @author admin
 */
@Controller
public class UserController {

    @RequestMapping("/quick")
    public String quick(){
        return "success";
    }
}

SpringMVC的数据响应

SpringMVC的数据响应方式

  1. 页面跳转
    • 直接返回字符串
    • 通过ModelAndView对象返回
  2. 回写数据
    • 直接返回字符串
    • 返回对象或集合

返回字符串形式

直接返回字符串:此种方式会将返回的字符串与视图解析器的前后缀拼接后跳转

image

返回带有前缀的字符串:

转发:forward:/jsp/success.jsp

重定向:redirect:/jsp/success.jsp

返回ModelAndView对象

@RequestMapping("/quick3")
public String quick3(Model model){
    model.addAttribute("hello","hello,SpringMVC!");
    return "success";
}

@RequestMapping("/quick2")
public ModelAndView quick2(ModelAndView modelAndView){
    modelAndView.addObject("hello","hello,SpringMVC!");
    modelAndView.setViewName("success");
    return modelAndView;
}

回写数据

直接返回字符串

web基础阶段,客户端访问服务器端,如果想直接回写字符串作为响应体返回的话,只需要使用response.getWriter().print("hello world")即可,name在Controller中想直接回写字符串该怎么办呢?

1. 通过SpringMVC框架注入的response对象,使用response.getWriter().print("hello world")回写数据,此时不需要视图跳转,业务方法返回值为void。
2. 将需要回写的字符串直接返回,但此时需要通过@ResponseBody注解告知SpringMVC框架,方法返回的字符串不是跳转而是直接在http响应体中返回。

导入maven依赖

<!-- https://mvnrepository.com/artifact/com.fasterxml.jackson.core/jackson-databind -->
<dependency>
    <groupId>com.fasterxml.jackson.core</groupId>
    <artifactId>jackson-databind</artifactId>
    <version>2.12.3</version>
</dependency>
<!-- https://mvnrepository.com/artifact/com.fasterxml.jackson.core/jackson-core -->
<dependency>
    <groupId>com.fasterxml.jackson.core</groupId>
    <artifactId>jackson-core</artifactId>
    <version>2.12.3</version>
</dependency>
<!-- https://mvnrepository.com/artifact/com.fasterxml.jackson.core/jackson-annotations -->
<dependency>
    <groupId>com.fasterxml.jackson.core</groupId>
    <artifactId>jackson-annotations</artifactId>
    <version>2.12.3</version>
</dependency>

UserController

@RequestMapping("/quick6")
@ResponseBody
public String quick6() throws JsonProcessingException {
    User user = new User();
    user.setId(1);
    user.setUsername("zhangsan");
    user.setPassword("111111");
    //使用json的转换工具将对象转换成json格式字符串再返回
    ObjectMapper mapper = new ObjectMapper();
    String json = mapper.writeValueAsString(user);
    return json;
}

返回对象或集合

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

<!--mvc的注解驱动-->
<mvc:annotation-driven/>

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

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

同时使用<mvc:annotation-driven/>默认底层就会集成jackson进行对象或集合的json格式字符串的转换。

SpringMVC获得请求数据

获得请求参数

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

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

  • 基本类型参数
  • pojo类型参数
  • 数组类型参数
  • 集合类型参数

获得基本类型数据

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

UserController

@RequestMapping("/quick8")
@ResponseBody
public User quick8(int id,String username,String password){
    User user = new User();
    user.setId(id);
    user.setUsername(username);
    user.setPassword(password);
    return user;
}

请求url

http://localhost:8080/quick8?id=1&username=zhangsan&password=111111

获得pojo类型数据

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

User

package com.example.domain;

/**
 * @author admin
 */
public class User {
    private int id;
    private String username;
    private String password;

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

    public int getId() {
        return id;
    }

    public void setId(int id) {
        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;
    }
}

UserController

@RequestMapping("/quick9")
@ResponseBody
public User quick9(User user){
    return user;
}

请求url

http://localhost:8080/quick9?id=1&username=zhangsan&password=111111

获得数组类型参数

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

UserController

@RequestMapping("/quick10")
@ResponseBody
public List<String> quick9(String[] list){
    return Arrays.asList(list);
}

请求url

http://localhost:8080/quick10?list=嘻嘻&list=哈哈&list=嘿嘿

获得集合类型参数

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

请求数据乱码问题

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

web.xml

<!--中文乱码过滤器-->
<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>

参数绑定注解@RequestParam

注解@RequestParam还有如下参数可以使用:

  • value:请求参数名称
  • required:指定的请求参数是否必须包括,默认是true,提交时如果没有此参数则报错
  • defaultValue:当没有指定请求参数时,则使用指定的默认参数值

UserController

@RequestMapping("/quick12")
@ResponseBody
public User quick12(@RequestParam("uname") String username,String password){
    User user = new User();
    user.setId(1);
    user.setUsername(username);
    user.setPassword(password);
    return user;
}

请求url

http://localhost:8080/quick12?uname=zhangsan&password=111111

获得Restful风格的参数

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

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

  • get:用于获取资源
  • post:用于新建资源
  • put:用于更新资源
  • delete:用于删除资源

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

UserController

@RequestMapping("/quick13/{username}")
@ResponseBody
public String quick13(@PathVariable(name="username") String username){
    return username;
}

请求url

http://localhost:8080/quick13/zhangsan

自定义类型转换器

  • SpringMVC默认已经提供了一些常用的类型转换器,例如客户端提交的字符串转换成int型进行参数设置。
  • 但是不是所有的数据类型都提供了转换器,没有提供的就需要自定义转换器,例如:日期类型的数据就需要自定义转换器

自定义类型转换器的开发步骤

  1. 定义转换器类实现Converter接口
  2. 在配置文件中声明转换器
  3. <annotation-driver/>

DataConverter

package com.example.converter;

import org.springframework.core.convert.converter.Converter;

import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;

/**
 * @author admin
 */
public class DateConverter implements Converter<String, Date> {
    public Date convert(String dateStr) {
        try {
            SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
            Date date = sdf.parse(dateStr);
            return date;
        } catch (ParseException e) {
            e.printStackTrace();
            return null;
        }
    }
}

spring-mvc.xml

<!--注册转换器-->
<bean name="conversionService" class="org.springframework.context.support.ConversionServiceFactoryBean">
    <property name="converters">
        <list>
            <bean class="com.example.converter.DateConverter"/>
        </list>
    </property>
</bean>

<!--配置mvc注解驱动-->
<mvc:annotation-driven conversion-service="conversionService"/>

获得Servlet相关API

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

  • HttpServletRequest
  • HttpServletResponse
  • HttpSession

UserController

@RequestMapping("/quick15")
@ResponseBody
public void quick15(HttpServletRequest req, HttpServletResponse resp, HttpSession session){
    System.out.println(req);
    System.out.println(resp);
    System.out.println(session);
}

获得请求头

@RequestHeader

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

@RequestHeader注解的属性如下:

  • value:请求头的名称
  • required:是否必须携带此请求头

@CookieValue

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

@CookieValue注解的属性如下:

  • value:指定cookie的名称
  • required:是否必须携带此cookie

文件上传

  1. 文件上传客户端三要素
    • 表单项type="file"
    • 表单的提交方式是post
    • 表单的entype属性是多部分表单形式,及enctype="multipart/form-data"
  2. 文件上传原理
    • 当form表单修改为多部分表单时,request.getParameter()将失效
    • enctype="application/x-www-form-urlencoded"时,form表单的正文内容格式是:key=value&key=value&key=value
    • 当form表单的enctype取值为Multipart/form-data时,请求正文内容就变成多部分形式

单文件上传步骤

  1. 导入fileupload和io坐标
  2. 配置文件上传解析器
  3. 编写文件上传代码

maven依赖

<!-- https://mvnrepository.com/artifact/commons-io/commons-io -->
<dependency>
    <groupId>commons-io</groupId>
    <artifactId>commons-io</artifactId>
    <version>2.6</version>
</dependency>
<!-- https://mvnrepository.com/artifact/commons-fileupload/commons-fileupload -->
<dependency>
    <groupId>commons-fileupload</groupId>
    <artifactId>commons-fileupload</artifactId>
    <version>1.3.3</version>
</dependency>

UserController

@RequestMapping("/quick16")
@ResponseBody
public void quick16(String fileName, MultipartFile file) throws IOException {
    System.out.println(fileName);
    //获得上传文件的名称
    String uploadFileName = file.getOriginalFilename();
    //将文件上传到指定位置
    file.transferTo(new File("D:\\"+uploadFileName));
}

upload.jsp

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>上传</title>
</head>
<body>
<form action="${pageContext.request.contextPath}/quick16" method="post" enctype="multipart/form-data">
    <p>名称: <input type="text" name="fileName"></p>
    <p>文件: <input type="file" name="file"></p>
    <input type="submit" value="上传">
</form>
</body>
</html>

多文件上传以此类推

Spring JdbcTemplate基本使用

JdbcTemplate概述

它是Spring框架中提供的一个对象,是对原始繁琐的JdbcAPI对象的简单封装。spring框架为我们提供了很多的操作模板类。例如:操作关系型数据的JdbcTemplate和HibernateTemplate,操作nosql数据库的RedisTemplate,操作消息队列的JmsTemplate等等。

JdbcTemplate开发步骤

  1. 导入spring-jdbc和spirng-tx坐标
  2. 创建数据库表和实体
  3. 创建JdbcTemplate对象
  4. 执行数据库操作

Spring产生JdbcTemplate对象

我们可以将JdbcTemplate的创建权交给了Spring,将数据源DataSource的创建权也交给了Spring,在Spring容器内部将数据源DataSource注入到JdbcTemplate模板对象中,配置如下:

maven依赖

<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-jdbc</artifactId>
    <version>5.2.9.RELEASE</version>
</dependency>
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-tx</artifactId>
    <version>5.2.9.RELEASE</version>
</dependency>

applicationContext.xml

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

    <context:property-placeholder location="classpath:db.properties"/>

    <bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
        <property name="driverClass" value="${jdbc.driver}"/>
        <property name="jdbcUrl" value="${jdbc.url}"/>
        <property name="user" value="${jdbc.username}"/>
        <property name="password" value="${jdbc.password}"/>
    </bean>

    <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
        <property name="dataSource" ref="dataSource"/>
    </bean>

</beans>

测试

@Test
public void testJdbcTemplateByIoc(){
    ApplicationContext context=new ClassPathXmlApplicationContext("applicationContext.xml");
    JdbcTemplate jdbcTemplate = context.getBean("jdbcTemplate", JdbcTemplate.class);
    int row = jdbcTemplate.update("insert into account(name,money) values(?,?)", "lisi", 5000);
    System.out.println("数据库受影响的行数: "+row);
}

JdbcTemplate的CRUD操作

Account

package com.example.pojo;

/**
 * @author admin
 */
public class Account {
    private int id;
    private String name;
    private Double money;

    @Override
    public String toString() {
        return "Account{" +
                "id=" + id +
                ", name='" + name + '\'' +
                ", money=" + money +
                '}';
    }

    public int getId() {
        return id;
    }

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

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public Double getMoney() {
        return money;
    }

    public void setMoney(Double money) {
        this.money = money;
    }
}

测试

package org.example.test;

import com.example.pojo.Account;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.BeanPropertyRowMapper;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

import java.util.List;

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:applicationContext.xml")
public class jdbcTemplateCRUDTest {

    @Autowired
    private JdbcTemplate jdbcTemplate;

    @Test
    public void testGetAccountCount(){
        Long count = jdbcTemplate.queryForObject("select count(id) from account", Long.class);
        System.out.println("数据库中有"+count+"条记录");
    }

    @Test
    public void testGetAccountById(){
        Account account = jdbcTemplate.queryForObject("select * from account where id=?", new BeanPropertyRowMapper<Account>(Account.class), 1);
        System.out.println(account);
    }

    @Test
    public void testGetAllAccount(){
        List<Account> accountList = jdbcTemplate.query("select * from account", new BeanPropertyRowMapper<Account>(Account.class));
        for (Account account : accountList) {
            System.out.println(account);
        }
    }

    @Test
    public void testDelete(){
        int row = jdbcTemplate.update("delete from account where id=?", 110);
        System.out.println("数据库中受影响的行数: "+row);
    }

    @Test
    public void testUpdate(){
        int row = jdbcTemplate.update("update account set money=? where name=?", 5000, "zhangsan");
        System.out.println("数据库中受影响的行数: "+row);
    }
}

SpringMVC拦截器

拦截器(interceptor)的作用

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

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

拦截器和过滤器的区别

区别 过滤器(Filter) 拦截器(Interceptor)
使用范围 是servlet规范中的一部分,任何Java Web工程都可以使用 是SpringMVC框架自己的,只有使用了SpringMVC框架的工程才能用
拦截范围 在url-pattern中配置了 /* 之后,可以对所有要访问的资源拦截 在<mvc:mapping path=""/>中配置了 /** 之后,也可以对所有资源进行拦截,但是可以通过<mvc:exclude-mapping path=""/>标签排除不需要拦截的资源

拦截器快速入门

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

  1. 创建拦截器类实现HandlerInterceptor接口
  2. 配置拦截器
  3. 测试拦截器的拦截效果

MyInterceptor1

package com.example.interceptor;

import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;

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

/**
 * @author admin
 */
public class MyInterceptor1 implements HandlerInterceptor {

    /**
     * 目标方法执行之前执行
     * @param request
     * @param response
     * @param handler
     * @return
     * @throws Exception
     */
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        System.out.println("preHandle1......");
        return true;
    }

    /**
     * 目标方法执行之后,视图返回之前执行
     * @param request
     * @param response
     * @param handler
     * @param modelAndView
     * @throws Exception
     */
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
        System.out.println("postHandle1......");
    }

    /**
     * 在整个流程都执行完毕后执行
     * @param request
     * @param response
     * @param handler
     * @param ex
     * @throws Exception
     */
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        System.out.println("afterCompletion1......");
    }
}

MyInterceptor2

package com.example.interceptor;

import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;

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

/**
 * @author admin
 */
public class MyInterceptor2 implements HandlerInterceptor {

    /**
     * 目标方法执行之前执行
     * @param request
     * @param response
     * @param handler
     * @return
     * @throws Exception
     */
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        System.out.println("preHandle2......");
        return true;
    }

    /**
     * 目标方法执行之后,视图返回之前执行
     * @param request
     * @param response
     * @param handler
     * @param modelAndView
     * @throws Exception
     */
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
        System.out.println("postHandle2......");
    }

    /**
     * 在整个流程都执行完毕后执行
     * @param request
     * @param response
     * @param handler
     * @param ex
     * @throws Exception
     */
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        System.out.println("afterCompletion2......");
    }
}

spring-mvc.xml

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

HelloController

@RequestMapping("/hello")
public ModelAndView hello(ModelAndView modelAndView){
    System.out.println("请求资源执行");
    modelAndView.addObject("hello","hello,SpringMVC-interceptor");
    modelAndView.setViewName("index");
    return modelAndView;
}

结果

image

结论

拦截器的执行顺序有配置顺序决定,即谁先配置谁先执行

拦截器方法说明

方法名 说明
preHandle() 方法将在处理之前进行调用,该方法的返回值是布尔值boolean类型的,当它返回false时,表示请求结束,后续的Interceptor和Controller都不会再执行;当返回值为true时就会继续调用下一个Interceptor的preHandle方法
postHandle() 该方法是在当前请求进行处理之后被调用,前提是preHandle方法的返回值为true时才能被调用,且它会在DispatcherServlet进行视图返回渲染之前被调用,所以我们可以在这个方法中对Controller处理之后的ModelAndView对象进行操作
afterCompletion() 该方法将在整个请求结束之后,也就是在DispatcherServlet渲染了对应的视图之后执行,前提是preHandle方法的返回值为true时才能被调用

SpringMVC异常处理

异常处理的思路

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

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

image

异常处理的两种方式

  • 使用SpringMVC提供的简单异常处理器SimpleMappingExceptionResolver
  • 实现Spring的异常处理接口HandlerExceptionResolver自定义自己的异常处理器

简单异常处理器SimpleMappingExceptionResolver

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

MyException

package com.example.exception;

/**
 * @author admin
 */
public class MyException extends Exception {
}

ExceptionServiceImpl

package com.example.service.impl;

import com.example.exception.MyException;
import com.example.service.ExceptionService;

import java.io.File;

/**
 * @author admin
 */
public class ExceptionServiceImpl implements ExceptionService {
    public void exception1() {
        System.out.println("类型转换异常...");
        Object s="哈喽";
        Integer num=(Integer) s;
    }

    public void exception2() {
        System.out.println("除零异常...");
        int i=1/0;
    }

    public void exception3() {
        System.out.println("文件找不到异常...");
        File file = new File("C:/xxx/xxx/xxx.txt");
    }

    public void exception4() {
        System.out.println("空指针异常...");
        String s=null;
        s.length();
    }

    public void exception5() throws MyException {
        System.out.println("自定义异常");
        throw new MyException();
    }
}

ExceptionController

package com.example.controller;

import com.example.exception.MyException;
import com.example.service.ExceptionService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;

/**
 * @author admin
 */
@Controller
public class ExceptionController {

    @Autowired
    private ExceptionService exceptionService;

    @RequestMapping("/show")
    public String showException() throws MyException {
        exceptionService.exception5();
        return "index";
    }
}

spring-mvc.xml

<!--配置简单异常处理器-->
<bean class="org.springframework.web.servlet.handler.SimpleMappingExceptionResolver">
    <!--如果与下面的异常匹配不到,就跳转到默认的错误页面-->
    <property name="defaultErrorView" value="error"/>
    <property name="exceptionMappings">
        <map>
            <!--如果是类型转换异常就跳转到exception-cast页面-->
            <entry key="java.lang.ClassCastException" value="exception-cast"/>
            <entry key="com.example.exception.MyException" value="exception-custom"/>
        </map>
    </property>
</bean>

自定义异常处理步骤

  1. 创建异常处理器类实现HandlerExceptionResolver
  2. 配置异常处理器
  3. 编写异常页面
  4. 测试异常跳转

MyExceptionResolver

package com.example.resolver;

import com.example.exception.MyException;
import org.springframework.web.servlet.HandlerExceptionResolver;
import org.springframework.web.servlet.ModelAndView;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.FileNotFoundException;

/**
 * @author admin
 */
public class MyExceptionResolver implements HandlerExceptionResolver {
    public ModelAndView resolveException(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, Exception e) {
        ModelAndView modelAndView = new ModelAndView();
        if(e instanceof MyException){
            modelAndView.addObject("info","自定义异常");
        }
        else if(e instanceof ClassCastException){
            modelAndView.addObject("info","类转换异常");
        }
        else if(e instanceof NullPointerException){
            modelAndView.addObject("info","空指针异常");
        }
        else if(e instanceof FileNotFoundException){
            modelAndView.addObject("info","文件未找到异常");
        }
        modelAndView.setViewName("error");
        return modelAndView;
    }
}

spring-mvc.xml

<!--配置自定义异常处理器-->
<bean class="com.example.resolver.MyExceptionResolver"/>

Spring的AOP简介

什么是AOP

AOP为Aspect Oriented Programming的缩写,意思为面向切面编程,是通过预编译方式和运行期动态代理实现程序功能的统一维护的一种技术。

AOP是OOP的延续,是软件开发中的一个热点,也是Spring框架中的一个重要内容,是函数式编程的一种衍生范型。利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。

AOP的作用及其优势

  • 作用:在程序运行期间,在不修改源码的情况下对方法进行功能增强
  • 优势:减少重复代码,提高开发效率,并且便于维护

AOP的底层实现

实际上,AOP的底层实现是通过Spring提供的动态代理技术实现的。在运行期间,Spring通过动态代理技术动态的生成代理对象,代理对象方法执行时进行增强功能的介入,在去调用目标对象的方法,从而完成功能的增强。

AOP的动态代理技术

常用的动态代理技术

  • JDK代理:基于接口的动态代理技术
  • cglib代理:基于父类的动态代理技术

image

image

JDK的动态代理

TargetInterface

package com.example.proxy.jdk;

/**
 * @author admin
 */
public interface TargetInterface {
    /**
     * 测试方法
     */
    void save();
}

Target

package com.example.proxy.jdk;

/**
 * @author admin
 */
public class Target implements TargetInterface {
    public void save() {
        System.out.println("save running......");
    }
}

Advice

package com.example.proxy.jdk;

/**
 * @author admin
 */
public class Advice {
    public void before(){
        System.out.println("前置增强......");
    }
    public void afterReturning(){
        System.out.println("后置增强......");
    }
}

ProxyTest

package com.example.proxy.jdk;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

/**
 * @author admin
 */
public class ProxyTest {
    public static void main(String[] args) {
        final Target target = new Target();
        //增强对象
        final Advice advice = new Advice();
        //返回值就是动态生成的代理对象
        TargetInterface proxy = (TargetInterface) Proxy.newProxyInstance(
                target.getClass().getClassLoader(),//目标对象类加载器
                target.getClass().getInterfaces(),//目标对象相同的接口字节码对象数组
                new InvocationHandler() {
                    //调用代理对象的任何方法,实质执行的都是invoke方法
                    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                        //前置增强
                        advice.before();
                        Object value = method.invoke(target, args);//执行目标方法
                        //后置增强
                        advice.afterReturning();
                        ;
                        return value;
                    }
                }
        );
        proxy.save();
    }
}

image

cglib的动态代理

Target

package com.example.proxy.cglib;

/**
 * @author admin
 */
public class Target {

    public void save(){
        System.out.println("save running......");
    }
}

Advice

package com.example.proxy.cglib;

/**
 * @author admin
 */
public class Advice {

    public void before(){
        System.out.println("前置增强......");
    }

    public void afterReturning(){
        System.out.println("后置增强......");
    }
}

ProxyTest

package com.example.proxy.cglib;

import org.springframework.cglib.proxy.Enhancer;
import org.springframework.cglib.proxy.MethodInterceptor;
import org.springframework.cglib.proxy.MethodProxy;

import java.lang.reflect.Method;

/**
 * @author admin
 */
public class ProxyTest {
    public static void main(final String[] args) {
        //目标对象
        final Target target = new Target();
        //曾强对象
        final Advice advice = new Advice();
        //1. 设置增强器
        Enhancer enhancer = new Enhancer();
        //2. 设置父类(目标)
        enhancer.setSuperclass(Target.class);
        //3. 设置回调
        enhancer.setCallback(new MethodInterceptor() {
            public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
                advice.before();
                Object value = method.invoke(target, objects);
                advice.afterReturning();
                return value;
            }
        });
        //4. 创建代理对象
        Target proxy = (Target) enhancer.create();
        proxy.save();
    }
}

AOP相关概念

Spring的AOP实现底层就是对上面的动态代理的代码进行了封装,封装后我们只需要对需要关注的部分进行代码编写,并通过配置的方法完成指定目标的方法增强。

在正式讲解AOP的操作之前,我们必须理解AOP的相关术语,常用的术语如下:

  • Target(目标对象):代理的目标对象
  • Proxy(代理):一个类被AOP织入增强后,就产生一个结果代理类
  • Joinpoint(连接点):所谓连接点是指那些被拦截到的点。在spring中,这些点指的是方法,因为spring只支持方法类型的连接点
  • Pointcut(切入点):所谓切入点是指我们要对哪些Joinpoint进行拦截的定义
  • Advice(通知/增强):所谓通知是指拦截到Joinpoint之后所要做的事情就是通知
  • Aspect(切面):是切入点和通知(引介)的结合
  • Weaving(织入):是指把增强应用到目标对象来创建新的代理对象的过程。spring采用动态代理织入,而AspectJ采用编译期织入和类装载期织入

AOP开发明确的事项

  1. 需要编写的内容

    • 编写核心业务代码(目标类的目标方法)
    • 编写切面类,切面类中有通知(增强功能方法)
    • 在配置文件中,配置织入关系,即将哪些通知连接点进行结合
  2. AOP技术实现的内容

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

  3. AOP底层使用哪种代理方式

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

基于xml的AOP开发

  1. 快速入门
    1. 导入AOP相关坐标
    2. 创建目标接口和目标类(内部有切点)
    3. 创建切面类(内部有增强方法)
    4. 将目标类和切面类的对象创建权交给spring
    5. 在applicationContext.xml中配置织入关系
    6. 测试代码

Maven依赖

<!-- https://mvnrepository.com/artifact/org.aspectj/aspectjweaver -->
<dependency>
    <groupId>org.aspectj</groupId>
    <artifactId>aspectjweaver</artifactId>
    <version>1.9.6</version>
    <scope>runtime</scope>
</dependency>

TargetInterface

package com.example.aop;

/**
 * @author admin
 */
public interface TargetInterface {
    /**
     * 测试方法
     */
    void save();
}

Target

package com.example.aop;

/**
 * @author admin
 */
public class Target implements TargetInterface {
    public void save() {
        System.out.println("save running......");
    }
}

MyAspect

package com.example.aop;

/**
 * @author admin
 */
public class MyAspect {

    public void before(){
        System.out.println("前置增强......");
    }

    public void afterReturning(){
        System.out.println("后置增强......");
    }
}

applicationContext.xml

<!--目标对象-->
<bean id="target" class="com.example.aop.Target"/>

<!--切面对象-->
<bean id="myAspect" class="com.example.aop.MyAspect"/>

<!--配置织入-->
<aop:config>
    <!--声明切面-->
    <aop:aspect ref="myAspect">
        <!--切面=切点+通知-->
        <aop:before method="before" pointcut="execution(public void com.example.aop.Target.save())"/>
    </aop:aspect>
</aop:config>

测试

package com.example.aop;

import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:applicationContext.xml")
public class AopTest {

    @Autowired
    private TargetInterface target;

    @Test
    public void testBefore(){
        target.save();
    }
}
  1. XML配置AOP详解

    1. 切入点表达式的写法

      表达式语法:

      execution([修饰符] 返回值类型 包名.类名.方法名(参数))

    • 访问修饰符可以省略
    • 返回值类型、包名、类名、方法名可以使用星号* 代表任意
    • 包名与类名之间一个点 . 代表当前包下的类,两个点 .. 表示当前包及其包下的类
    • 参数列表可以使用两个点 .. 表示任意个数,任意类型的参数列表

    例如:

    execution(public void com.example.aop.Target.method())
    execution(void com.example.aop.Target.*(..))
    execution(* com.example.aop.*.*(..))
    execution(* com.example.aop..*.*(..))
    execution(* *..*.*(..))
    
    1. 通知的类型

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

      名称 标签 说明
      前置通知 <aop:before> 用于配置前置通知。指定增强的方法在切入点方法之前执行
      后置通知 <aop:after-returning> 用于配置后置通知。指定增强的方法在切入点方法之后执行
      环绕通知 <aop:around> 用于配置环绕通知。指定增强的方法在切入点方法之前和之后都执行
      异常抛出通知 <aop:throwing> 用于配置异常抛出通知。指定增强的方法在出现异常时执行
      最终通知 <aop:after> 用于配置最终通知。无论增强方式执行是否有异常都会执行
  2. 切入点表达式的抽取

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

<!--配置织入-->
<aop:config>
    <!--声明切面-->
    <aop:aspect ref="myAspect">
        <!--配置切入点-->
        <aop:pointcut id="myPointcut" expression="execution(* com.example.aop.*.*(..))"/>
        <!--切面=切点+通知-->
        <aop:before method="before" pointcut-ref="myPointcut"/>
        <aop:after-returning method="afterReturning" pointcut-ref="myPointcut"/>
        <aop:around method="around" pointcut-ref="myPointcut"/>
        <aop:after-throwing method="afterThrowing" pointcut-ref="myPointcut"/>
        <aop:after method="after" pointcut-ref="myPointcut"/>
    </aop:aspect>
</aop:config>

基于注解的AOP开发

  1. 快速入门

    基于注解的aop开发步骤:

    1. 创建目标接口和目标类(内部有切点)
    2. 创建切面类(内部有增强方法)
    3. 将目标类和切面类的对象创建权交给spring
    4. 在切面类中使用注解配置织入关系
    5. 在配置文件中开启组件扫描和AOP的自动代理
    6. 测试

    TargetInterface

package com.example.anno;

/**
 * @author admin
 */
public interface TargetInterface {
    /**
     * 测试方法
     */
    void save();
}

Target

package com.example.anno;

import org.springframework.stereotype.Component;

/**
 * @author admin
 */
@Component
public class Target implements TargetInterface {
    public void save() {
        //int i=1/0;
        System.out.println("save running......");
    }
}

MyAspect

package com.example.anno;

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.stereotype.Component;

/**
 * @Aspect 标注当前MyAspect是一个切面类
 * @author admin
 */
@Component
@Aspect
public class MyAspect {

    @Before("execution(* com.example.anno.*.*(..))")
    public void before(){
        System.out.println("前置增强......");
    }

    public void afterReturning(){
        System.out.println("后置增强......");
    }

    public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
        System.out.println("环绕前通知......");
        Object result = joinPoint.proceed();
        System.out.println("环绕后通知......");
        return result;
    }

    public void afterThrowing(){
        System.out.println("异常抛出通知......");
    }

    public void after(){
        System.out.println("最终通知......");
    }
}

测试

package com.example.aop;

import com.example.anno.TargetInterface;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:applicationContext.xml")
public class AopTest {

    @Autowired
    private TargetInterface target;

    @Test
    public void testBefore(){
        target.save();
    }
}
  1. 注解配置AOP详解

    注解通知的类型

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

名称 注解 说明
前置通知 @Before 用于配置前置通知。指定增强的方法在切入点方法之前执行
后置通知 @AfterReturning 用于配置后置通知。指定增强的方法在切入点方法之后执行
环绕通知 @Around 用于配置环绕通知。指定增强的方法在切入点方法之前和之后都执行
异常抛出通知 @AfterThrowing 用于配置异常抛出通知。指定增强的方法在出现异常时执行
最终通知 @After 用于配置最终通知。无论增强方式执行是否有异常都会执行

切点表达式的抽取

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

@Pointcut("execution(* com.example.anno.*.*(..))")
public void pointcut(){}

@Before("pointcut()")
public void before(){
    System.out.println("前置增强......");
}

Spring的事务控制

编程式事务控制相关对象

  1. PlatformTransactionManager

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

方法 说明
TransactionStatus getTransaction(TransactionDefination defination) 获取事务的状态信息
void commit(TransactionStatus status) 提交事务
void rollback(TransactionStatus status) 回滚事务

注意:

PlatfromTransactionManager是接口类型,不同的Dao层技术则有不同的实现类,例如:

Dao层技术是jdbc或mybatis时:org.springframework.jdbc.datasource.DataSourceTransactionManager

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

  1. TransactionDefinition

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

方法 说明
int getIsolationLevel() 获得事务的隔离级别
int getPropogationBehavior() 获得事务的传播行为
int getTimeout() 获得超时时间
boolean isReadOnly() 是否只读

事务的隔离级别

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

  • ISOLATION_DEFAULT
  • ISOLATION_READ_UNCOMMITTED
  • ISOLATION_READ_COMMITTED
  • ISOLATION_REPEATABLE_READ
  • ISOLATION_SERIALIZABLE

事务的传播行为

  • REQUIRED:如果当前没有事务,就新建一个事务,如果已经存在一个事务中,加入到这个事务中,一般的选择(默认值)
  • SUPPORTS:支持当前事务,如果当前没有事务,就以非事务方式执行(没有事务)
  • MANDATORY:使用当前的事务,如果当前没有事务,就抛出异常
  • REQUERS_NEW:新建事务,如果当前在事务中,把当前事务挂起
  • NOT_SUPPORTED:以非事务方式执行操作,如果当前存在事务,就把当前事务挂起
  • NEVER:以非事务方式运行,如果当前存在事务,抛出异常
  • NESTED:如果当前存在事务,则在嵌套事务内执行。如果当前没有事务,则执行REQUIRED类似的操作
  • 超时时间:默认值是-1,没有超时限制。如果有,以秒为单位进行设置
  • 是否只读:建议查询时设置为只读
  1. TransactionStatus

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

方法 说明
boolean hasSavepoint() 是否存储回滚点
booleanisCompleted() 是否是否完成
boolean isNewTransaction() 是否是新事务
boolean isRollbackOnly() 事务是否回滚

基于xml的声明式事务控制

  1. 什么是声明式事务控制

Spring的声明式事务顾名思义就是采用声明的方式来处理事务。这里所说的声明,就是指在配置文件中声明,用在Spring配置文件中声明式的处理事务来代替代码式的处理事务。

声明式事务处理的作用

  • 事务管理不侵入开发的组件。具体来说,业务逻辑对象就不会意识到正在事务管理之中,事实上也应该如此,因为事务管理是属性系统层面的服务,而不是业务逻辑的一部分,如果想要改变事务管理策划的话,也只需要在定义文件中重新配置即可。
  • 在不需要事务管理的时候,只要在设定文件上修改一下,即可移去事务管理服务,无需改变代码重新编译,这样维护起来极其方便。

注意:Spring声明式事务控制底层就是AOP

  1. 切点方法的事务参数的配置
<!--配置平台事务管理器-->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
    <property name="dataSource" ref="dataSource"/>
</bean>

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

<!--配置事务的aop织入-->
<aop:config>
    <aop:pointcut id="accountServicePointcut" expression="execution(* com.example.service.impl.*.*(..))"/>
    <aop:advisor advice-ref="txAdvice" pointcut-ref="accountServicePointcut"/>
</aop:config>

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

<tx:method name="transfer" isolation="REPEATABLE_READ" propagation="REQUIRED" timeout="-1" read-only="false"/>

  • name:切点方法名称
  • isolation:事务的隔离级别
  • propogation:事务的传播行为
  • timeout:超时时间
  • read-only:是否只读

注解配置声明式事务控制解析

  1. 使用@Transactional在需要进行事务控制的类或是方法上修饰,注解可用的属性同xml配置方式,例如:隔离级别、传播行为等。
  2. 注解使用在类上,那么该类下的所有方法都使用同一套注解参数配置
  3. 使用在方法上,不同的方法可以采用不同的事务参数配置
  4. xml配置文件中要开启事务的注解驱动<tx:annotation-driven>
<!--配置平台事务管理器-->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
    <property name="dataSource" ref="dataSource"/>
</bean>

<!--开启注解事务支持-->
<tx:annotation-driven transaction-manager="transactionManager"/>

Mybatis简介

原始jdbc操作的分析

原始jdbc开发存在的问题如下:

  1. 数据库连接创建、释放频繁造成系统资源浪费从而影响系统性能
  2. sql语句在代码中硬编码,造成代码不易维护,实际应用sql变化的可能较大,sql变动需要改变java代码
  3. 查询操作时,需要手动将结果集中的数据手动封装到实体中。插入操作时,需要手动将实体的数据设置到sql语句的占位符位置

应对上述问题给出的解决方案:

  1. 使用数据库连接池初始化连接资源
  2. 将sql语句抽取到xml配置文件中
  3. 使用反射、内省等底层技术,自动将实体与表进行属性与字段的自动映射

什么是Mybatis

  • mybatis是一个优秀的基于java的持久层框架,它内部封装了jdbc,使开发者只需要关注sql语句本身,而不需要花费精力去处理加载驱动、创建连接、创建statement等繁杂的过程。
  • mybatis通过xml或注解的方式将要执行的各种statement配置起来,并通过java对象和statement中sql的动态参数进行映射生成最终执行的sql语句
  • 最后mybatis框架执行sql并将结果映射为java对象并返回。采用ORM思想解决了实体和数据库映射的问题,对jdbc进行了封装,屏蔽了jdbc api底层访问细节,使我们不用与jdbc api打交道,就可以完成对数据库的持久化操作。

Mybatis快速入门

Mybatis开发步骤

MyBatis中文网

mybatis开发步骤

  1. 添加mybatis的坐标
  2. 创建user数据表
  3. 编写User实体类
  4. 编写UserMapper接口
  5. 编写映射文件UserMapper.xml
  6. 编写核心文件SqlMapConfig.xml
  7. 编写测试类

mybatis坐标

<!-- https://mvnrepository.com/artifact/mysql/mysql-connector-java -->
<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <version>8.0.16</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.mybatis/mybatis -->
<dependency>
    <groupId>org.mybatis</groupId>
    <artifactId>mybatis</artifactId>
    <version>3.4.6</version>
</dependency>

User

package com.example.pojo;

import lombok.Data;

/**
 * @author admin
 */
@Data
public class User {
    private int id;
    private String username;
    private String password;
}

UserMapper

package com.example.mapper;

import com.example.pojo.User;

import java.util.List;

/**
 * @author admin
 */
public interface UserMapper {

    /**
     * 获取全部用户
     * @return
     */
    List<User> getAllUser();
}

UserMapper.xml

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.example.mapper.UserMapper">
    <select id="getAllUser" resultType="com.example.pojo.User">
        select * from user
    </select>
</mapper>

mybatisConfig.xml

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
        PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
    <properties resource="db.properties"/>
    <settings>
        <setting name="logImpl" value="STDOUT_LOGGING"/>
    </settings>
    <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.username}"/>
                <property name="password" value="${jdbc.password}"/>
            </dataSource>
        </environment>
    </environments>
    <mappers>
        <mapper resource="com\example\mapper\UserMapper.xml"/>
    </mappers>
</configuration>

测试

@Test
public void testGetAllUser() throws IOException {
    InputStream resource = Resources.getResourceAsStream("mybatisConfig.xml");
    SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(resource);
    SqlSession sqlSession = sqlSessionFactory.openSession();
    UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
    List<User> userList = userMapper.getAllUser();
    for (User user : userList) {
        System.out.println(user);
    }
    sqlSession.close();
}

结果

image

Mybatis的增删改查操作

Mybatis的插入数据操作

插入操作注意问题

  • 插入语句使用 insert 标签
  • 在映射文件中使用parameterType属性指定要插入的数据类型
  • sql语句中使用#{实体属性名}方式引用实体中的属性值
  • 插入操作涉及数据库数据变化,所以要使用sqlSession对象显式的提交事务,即sqlSession.commit()

UserMapper

package com.example.mapper;

import com.example.pojo.User;

import java.util.List;

/**
 * @author admin
 */
public interface UserMapper {

    /**
     * 增加用户
     * @param user
     * @return
     */
    int addUser(User user);
}

UserMapper.xml

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.example.mapper.UserMapper">
    <insert id="addUser" parameterType="com.example.pojo.User">
        insert into user(username,password) values(#{username},#{password})
    </insert>
</mapper>

测试

@Test
public void testAddUser() throws IOException {
    InputStream resource = Resources.getResourceAsStream("mybatisConfig.xml");
    SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(resource);
    SqlSession sqlSession = sqlSessionFactory.openSession();
    UserMapper mapper = sqlSession.getMapper(UserMapper.class);
    User user = new User();
    user.setUsername("xiaoba");
    user.setPassword("666666");
    int row = mapper.addUser(user);
    System.out.println("数据库中受影响的行数: "+row);
    sqlSession.commit();
    sqlSession.close();
}

Mybatis的修改数据操作

修改操作注意问题

  • 修改语句使用update标签
  • 在映射文件中使用parameterType属性指定要插入的数据类型
  • sql语句中使用#{实体属性名}方式引用实体中的属性值
  • 插入操作涉及数据库数据变化,所以要使用sqlSession对象显式的提交事务,即sqlSession.commit()

UserMapper

package com.example.mapper;

import com.example.pojo.User;

import java.util.List;

/**
 * @author admin
 */
public interface UserMapper {

    /**
     * 根据id修改用户信息
     * @param user
     * @return
     */
    int updateUserById(User user);
}

UserMapper.xml

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.example.mapper.UserMapper">
    <update id="updateUserById" parameterType="com.example.pojo.User">
        update user set username=#{username},password=#{password} where id=#{id}
    </update>
</mapper>

测试

@Test
public void testUpdateUserById() throws IOException {
    InputStream resource = Resources.getResourceAsStream("mybatisConfig.xml");
    SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(resource);
    SqlSession sqlSession = sqlSessionFactory.openSession();
    UserMapper mapper = sqlSession.getMapper(UserMapper.class);
    User user = new User();
    user.setId(100);
    int row = mapper.updateUserById(user);
    System.out.println("数据库中受影响的行数: "+row);
    sqlSession.commit();
    sqlSession.close();
}

Mybatis的删除数据操作

删除操作注意问题

  • 删除语句使用delete标签
  • sql语句中使用#{任意字符}方式引入传递的单个参数
  • 在映射文件中使用parameterType属性指定要插入的数据类型
  • 插入操作涉及数据库数据变化,所以要使用sqlSession对象显式的提交事务,即sqlSession.commit()

UserMapper

package com.example.mapper;

import com.example.pojo.User;

import java.util.List;

/**
 * @author admin
 */
public interface UserMapper {

    /**
     * 根据id删除用户
     * @param id
     * @return
     */
    int deleteUserById(int id);
}

UserMapper.xml

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.example.mapper.UserMapper">
    <delete id="deleteUserById" parameterType="_int">
        delete from user where id=#{id}
    </delete>
</mapper>

测试

@Test
public void testDeleteUserById() throws IOException {
    InputStream resource = Resources.getResourceAsStream("mybatisConfig.xml");
    SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(resource);
    SqlSession sqlSession = sqlSessionFactory.openSession();
    UserMapper mapper = sqlSession.getMapper(UserMapper.class);
    int row = mapper.deleteUserById(100);
    System.out.println("数据库中受影响的行数: "+row);
    sqlSession.commit();
    sqlSession.close();
}

Mybatis的数据查询操作

UserMapper

package com.example.mapper;

import com.example.pojo.User;

import java.util.List;

/**
 * @author admin
 */
public interface UserMapper {

    /**
     * 根据id查询用户
     * @param id
     * @return
     */
    User getUserById(int id);
}

UserMapper.xml

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.example.mapper.UserMapper">
    <select id="getUserById" parameterType="_int" resultType="com.example.pojo.User">
        select * from user where id=#{id}
    </select>
</mapper>

测试

@Test
public void testGetUserById() throws IOException {
    InputStream resource = Resources.getResourceAsStream("mybatisConfig.xml");
    SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(resource);
    SqlSession sqlSession = sqlSessionFactory.openSession();
    UserMapper mapper = sqlSession.getMapper(UserMapper.class);
    User user = mapper.getUserById(1);
    System.out.println(user);
    sqlSession.close();
}

Mybatis核心配置文件概述

Mybatis核心配置文件层级关系

image

Mybatis常用配置解析

  1. environments标签

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

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

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

  • UNPOOLED:这个数据源的实现只是每次被请求时打开和关闭连接
  • POOLED:这种数据源的实现利用"池"的概念将JDBC连接对象组织起来
  • JNDI:这个数据源的实现是为了能在如EJB或应用服务器这类容器中使用,容器可以集中或在外部配置数据源,然后放置一个JNDI上下文的引用
  1. mapper标签

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

  • 使用相对于类路径的资源引用,例如:<mapper resource="org/mybatis/builder/AuthorMapper.xml"/>
  • 使用完全限定资源定位符(URL),例如:<mapper url="file:///var/mappers/AuthorMapper.xml"/>
  • 使用映射器接口实现类的完全限定类名,例如:<mapper class="org.mybatis.builder.AuthorMapper"/>
  • 将包内的映射器接口实现全部注册为映射器,例如:<package name="org.mybatis.builder"/>
  1. Properties标签

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

<properties resource="db.properties"/> 

  1. typeAliases标签

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

<typeAliases>
    <!--配置单个别名-->
    <typeAlias type="com.example.pojo.User" alias="user"/>
    <!--包下的所有实体类都配置别名-->
    <package name="com.example.pojo"/>
</typeAliases>

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

别名 数据类型
string String
long Long
int Integer
double Double
boolean Boolean
... ...

Mybatis相应API

SqlSession工厂对象SqlSessionFactory

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

方法 解释
openSession() 会默认开启一个事务,但事务不会自动提交,也就意味着需要手动提交该事务,更新操作才会持久化到数据库中
openSession(boolean autoCommit) 参数为是否自动提交,如果设置为true,那么就不需要手动提交事务

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()

Mybatis映射文件深入

动态sql语句

  1. 动态sql语句概述

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

image

  1. 动态sql之<if>

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

<select id="getUserByCondition" resultType="user" parameterType="user">
    select * from user
    <where>
        <if test="id>0">
            and id=#{id}
        </if>
        <if test="username!=null">
            and username=#{username}
        </if>
        <if test="password!=null">
            and password=#{password}
        </if>
    </where>
</select>
  1. 动态sql之<foreach>

循环执行sql的拼接操作,例如:select * from user where id in (1,2,5)

<select id="getUserByRangeId" resultType="user" parameterType="list">
    select * from user
    <where>
        <foreach collection="list" open="id in(" close=")" separator="," item="id">
            #{id}
        </foreach>
    </where>
</select>
  1. sql片段抽取

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

Mybatis核心配置文件深入

typeHandlers标签

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

类型处理器 Java类型 JDBC类型
BooleanTypeHandler java.lang.Boolean.boolean 数据库兼容的BOOLEAN
ByteTypeHandler java.lang.Byte.byte 数据库兼容的NUMERIC或BYTE
ShortTypeHandler java.lang.Short.short 数据库兼容的NUMERIC或SHORT INTEGER
IntegerTypeHandler ava.lang.Integer.int 数据库兼容的NUMERIC或INTEGER
LongTypeHandler java.land.Long.long 数据库兼容的NUMERIC或LONG INTEGER

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

开发步骤:

  1. 定义转换类集成类BaseTypeHandler
  2. 覆盖4个未实现的方法,其中setNonNullParameter为Java程序设置数据到数据库的回调方法,getNullableResult为查询时mysql的字符串类型转换成Java的Type类型的方法
  3. 在Mybatis核心配置文件中进行注册
  4. 测试转换是否正确

DateTypeHandler

package com.example.handler;

import org.apache.ibatis.type.BaseTypeHandler;
import org.apache.ibatis.type.JdbcType;

import java.sql.CallableStatement;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.Date;

/**
 * @author admin
 */
public class DateTypeHandler extends BaseTypeHandler<Date> {

    /**
     * 将Java类型转换成数据库需要的类型
     * @param preparedStatement
     * @param i
     * @param date
     * @param jdbcType
     * @throws SQLException
     */
    @Override
    public void setNonNullParameter(PreparedStatement preparedStatement, int i, Date date, JdbcType jdbcType) throws SQLException {
        long time = date.getTime();
        String timeStr = String.valueOf(time);
        preparedStatement.setString(i,timeStr);
    }

    /**
     * 将数据库中的类型转换成Java类型
     * @param resultSet
     * @param s
     * @return
     * @throws SQLException
     */
    @Override
    public Date getNullableResult(ResultSet resultSet, String s) throws SQLException {
        String timeStr = resultSet.getString(s);
        long time = Long.parseLong(timeStr);
        Date date = new Date(time);
        return date;
    }

    /**
     * 将数据库中的类型转换成Java类型
     * @param resultSet
     * @param i
     * @return
     * @throws SQLException
     */
    @Override
    public Date getNullableResult(ResultSet resultSet, int i) throws SQLException {
        String timeStr = resultSet.getString(i);
        long time = Long.parseLong(timeStr);
        Date date = new Date(time);
        return date;
    }

    /**
     * 将数据库中的类型转换成Java类型
     * @param callableStatement
     * @param i
     * @return
     * @throws SQLException
     */
    @Override
    public Date getNullableResult(CallableStatement callableStatement, int i) throws SQLException {
        String timeStr = callableStatement.getString(i);
        long time = Long.parseLong(timeStr);
        Date date = new Date(time);
        return date;
    }
}

mybatisConfig.xml

<!--注册类型处理器-->
<typeHandlers>
    <typeHandler handler="com.example.handler.DateTypeHandler"/>
</typeHandlers>

测试

@Test
public void testGetPersonById() throws IOException {
    InputStream resource = Resources.getResourceAsStream("mybatisConfig.xml");
    SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(resource);
    SqlSession sqlSession = sqlSessionFactory.openSession();
    PersonMapper mapper = sqlSession.getMapper(PersonMapper.class);
    Person person = mapper.getPersonById(1);
    System.out.println(person);
    sqlSession.close();
}

@Test
public void testAddPerson() throws IOException {
    InputStream resource = Resources.getResourceAsStream("mybatisConfig.xml");
    SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(resource);
    SqlSession sqlSession = sqlSessionFactory.openSession();
    PersonMapper mapper = sqlSession.getMapper(PersonMapper.class);
    Person person = new Person();
    person.setName("张三");
    person.setGender(0);
    person.setAge(21);
    person.setBirthday(new Date());
    int row = mapper.addPerson(person);
    System.out.println("数据库受影响的条数: "+row);
    sqlSession.commit();
    sqlSession.close();
}

plugins标签

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

开发步骤:

  1. 导入通用PageHelper的坐标
  2. 在mybatis核心配置文件中配置PageHelper插件
  3. 测试分页数据获取

导入maven依赖

<!-- https://mvnrepository.com/artifact/com.github.jsqlparser/jsqlparser -->
<dependency>
    <groupId>com.github.jsqlparser</groupId>
    <artifactId>jsqlparser</artifactId>
    <version>3.1</version>
</dependency>
<!-- https://mvnrepository.com/artifact/com.github.pagehelper/pagehelper -->
<dependency>
    <groupId>com.github.pagehelper</groupId>
    <artifactId>pagehelper</artifactId>
    <version>5.2.0</version>
</dependency>

配置PageHelper插件

<!--配置分页助手插件-->
<plugins>
    <plugin interceptor="com.github.pagehelper.PageInterceptor">
        <!--pagehelper5.x.x以上不需要设置dialect属性-->
        <!--<property name="dialect" value="mysql"/>-->
    </plugin>
</plugins>

测试

@Test
public void testGetAllUser() throws IOException {
    InputStream resource = Resources.getResourceAsStream("mybatisConfig.xml");
    SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(resource);
    SqlSession sqlSession = sqlSessionFactory.openSession();
    //设置分页相关参数:当前页+每页显式的条数
    PageHelper.startPage(2,3);
    UserMapper mapper = sqlSession.getMapper(UserMapper.class);
    List<User> userList = mapper.getAllUser();
    for (User user : userList) {
        System.out.println(user);
    }
    //获得与分页相关参数
    PageInfo<User> pageInfo=new PageInfo<User>(userList);
    System.out.println("当前页: "+pageInfo.getPageNum());
    System.out.println("每页显式条数: "+pageInfo.getPageSize());
    System.out.println("总条数: "+pageInfo.getTotal());
    System.out.println("上一页: "+pageInfo.getPrePage());
    System.out.println("下一页: "+pageInfo.getNextPage());
    System.out.println("是否是第一页: "+pageInfo.isIsFirstPage());
    System.out.println("是否是最后一页: "+pageInfo.isIsLastPage());
    sqlSession.close();
}

Mybatis多表查询

一对一查询

  1. 一对一查询的模型

用户表和订单表的关系为,一个用户有多个订单,一个订单只从属于一个用户一对一查询的需求:查询一个订单,与此同时查询出该订单所属的用户

image

posted @ 2021-11-07 11:43  (x²+y²-1)³=x²y³  阅读(80)  评论(0)    收藏  举报