SSM总结笔记

SSM总结笔记

楠哥教你学Java Spring+SpringMVC+Mybatis总结笔记

Spring

Spring概述

  • Spring是一个企业级开发框架,关注点在于软件设计层面
  • Spring已经成为Java领域的行业标准
  • Spring 提供了各个层面的解决方案,Spring MVC、Spring Data、Spring Cloud
  • Spring两大核心机制loC(控制反转)、AOP(面向切面)

Spring 的优点

  • 低侵入式设计
  • 独立于各种应用服务器
  • 依赖注入特性将组件关系透明化,降低了耦合度
  • 面向切面编程特性允许将通用任务进行集中式处理
  • 与第三方框架的良好整合

Spring框架两大核心机制(loC、AOP)

  • loC(控制反转) / DI(依赖注入)
  • AOP(面向切面编程)

Spring 是一个**企业级开发框架**,是软件设计层面的框架,优势在于可以将应用程序进行分层,开发者可以自主选择组件。

企业级项目特点

  • 用户数量多、数据规模大、功能模块众多
  • 性能和安全要求高
  • 业务复杂
  • 灵活多变

MVC: Struts2、Spring MVC
ORMapping: Hibernate、MyBatis、Spring Data

SpringIoC

控制反转

传统的程序开发中,需要调用对象时,通常由调用者来创建被调用者的实例,即对象是由调用者主动new出来的。
但在Spring框架中创建对象的工作不再由调用者来完成,而是交给loC容器来创建,再推送给调用者,整个流程完成反转,所以是控制反转。

如何使用IoC
  • 创建Maven工程,pom.xml添加依赖
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.liner</groupId>
    <artifactId>ssmdemo</artifactId>
    <version>1.0-SNAPSHOT</version>


    <dependencies>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>5.3.19</version>
        </dependency>

    </dependencies>

    <properties>
        <maven.compiler.source>8</maven.compiler.source>
        <maven.compiler.target>8</maven.compiler.target>
    </properties>

</project>

  • 创建实体类Student
@Data
public class Student {
    private String name;
    private int age;
}
  • 传统开发方式,手动new一个Student
Student student = new Student();
student.setName("小张");
student.setAge (12);
system.out.println(student);
  • 使用控制反转,通过IoC创建Student,在配置文件中添加需要管理的对象,XML格式的配置文件,文件名可以自定义。
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">

    <bean id="student" class="com.liner.spring.springIoc.Student">
        <property name="name" value="小王"/>
        <property name="age" value="12"/>
    </bean>
</beans>
  • 从IoC中获取对象
ApplicationContext context = new ClassPathXmlApplicationContext("spring.xml");
Student student = (Student) context.getBean("student");
System.out.println(student);
配置文件
  • 通过配置bean标签来完成对象的管理。
    • id:对象名。
    • class:对象的模版类(所有交给loC容器来管理的类必须有无参构造函数,因为Spring 底层是通过反射机制来创建对象,调用的是无参构造)
  • 对象的成员变量通过property标签完成赋值。
    • name:成员变量名。
    • value:成员变量值(基本数据类型,String 可以直接赋值,如果是其他引用类型,不能通过value赋值)
    • ref:将loC中的另外一个bean赋给当前的成员变量(DI)
IoC底层原理
  • 读取配置文件,解析XML。
  • 通过反射机制实例化配置文件中所配置所有的bean。
public class ClassPathXmlApplicationContext implements ApplicationContext {

    private Map<String, Object> ioc = new HashMap<>();

    public ClassPathXmlApplicationContext(String path) {
        try {
            SAXReader saxReader = new SAXReader();
            Document document = saxReader.read("./src/main/resources/" + path);
            Element root = document.getRootElement();
            Iterator<Element> iterator = root.elementIterator();
            while (iterator.hasNext()) {
                Element element = iterator.next();
                String id = element.attributeValue("id");
                String className = element.attributeValue("class");
                //通过反射机制创建对象
                Class clazz = Class.forName(className);
                //获取无参构造 创建目标对象
                Constructor constructor = clazz.getConstructor();
                Object o = constructor.newInstance();

                //给目标对象赋值
                Iterator<Element> beanIter = element.elementIterator();
                while (beanIter.hasNext()) {
                    Element property = beanIter.next();
                    String name = property.attributeValue("name");
                    String valueStr = property.attributeValue("value");

                    String ref = property.attributeValue("ref");
                    if (ref == null) {  //普通赋值
                        //将首字母变大写
                        String methodName = "set" + name.substring(0, 1).toUpperCase() + name.substring(1);

                        Field field = clazz.getDeclaredField(name);
                        Method method = clazz.getDeclaredMethod(methodName, field.getType());
                        System.out.println(method);
                        //根据成员变量的类型将value进行转换
                        Object value = null;
                        System.out.println(field.getType().getName());

                        if (field.getType().getName() == "java.lang.String") {
                            value = valueStr;
                        }
                        if (field.getType().getName() == "int") {
                            value = Integer.parseInt(valueStr);
                        }
                        method.invoke(o, value);
                    }
                    ioc.put(id, o);
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    @Override
    public Object getBean(String id) {
        return ioc.get(id);
    }
}

  • 从IoC中获取对象
ApplicationContext context = new ClassPathXmlApplicationContext("spring.xml");
Student student = (Student) context.getBean("student");
System.out.println(student);

  • 通过运行时类获取bean
ApplicationContext context = new ClassPathXmlApplicationContext("spring.xml");
Student student = (Student) context.getBean(Student.class);
System.out.println(student);
//这种方式存在一个问题,配置文件中一个数据类型的对象只能有一个实例,否则会抛出异常,因为没有唯一的bean。

通过有参构造创建bean

  • 在实体类中创建对应的有参构造函数。
  • 配置文件
<bean id="student3" class="com.liner.spring.springIOC.Student">
    <constructor-arg name="id" value="3"></constructor-arg>
    <constructor-arg name="name" value="小明"></constructor-arg>
    <constructor-arg name="age" value="18"></constructor-arg>
    <constructor-arg name="address" ref="address"></constructor-arg>
</ bean>

给bean注入集合

<bean id="student" class="com.liner.spring.springIOC.Student">
    <property name="id" value="2"></property>
    <property name="name" value="李四"></property>
    <property name="age" value="23"></property>
    <property name="addresses">
        <list>
            <ref bean="address"></ref>
        </list>
    </property></ bean>
<bean id="address" class="com.liner.spring.springIOC.Address">
    <property name="id" value="1"></property>
    <property name="name" value="科技路"></property>
</bean>
scope 作用域

Spring 管理的bean是根据scope 来生成的,表示 bean的作用域,共4种。

  • singleton:单例,表示通过Spring容器获取的bean是唯一的。 默认值
  • prototype:原型,表示通过Spring容器获取的bean是不同的。
  • request:请求,表示在一次HTTP请求内有效。
  • session:回话,表示在一个用户会话内有效。

request和session 只适用于Web项目,大多数情况下,使用单例和原型较多。

  • prototype模式当业务代码获取loC容器中的bean时,Spring才去调用无参构造创建对应的bean
  • singleton模式无论业务代码是否获取loC容器中的bean,Spring 在加载spring.xml时就会创建bean
Spring的继承

与Java的继承不同,Java是==类层面的继承==,子类可以继承父类的内部结构信息;

Spring 是==对象层面的继承==,子对象可以继承父对象的属性值

<bean id="student" class="com.liner.spring.springIOC.Student">
   <property name="id" value="2"></property>
    <property name="name" value="李四"></property>
    <property name="age" value="23"></property>
</ bean>

<bean id="student2" class="com.liner.spring.springIOC.Student" parent="student">
    <property name="age" value="13"></property>
</ bean>

Spring 的继承关注点在于具体的对象,而不在于类,

即不同的两个类的实例化对象可以完成继承,前提是==子对象必须包含父对象的所有属性==,同时可以在此基础上添加其他的属性。

Spring的依赖

与继承类似,依赖也是描述bean和bean之间的一种关系,配置依赖之后,被依赖的bean一定先创建,再创建依赖的bean,A依赖于B,先创建B,再创建A。

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
    <bean id="student" class="com.liner.spring.springIOC.Student " depends-on="user"></bean>
   	<bean id="user" class="com.liner.spring.springIOC.User"></bean>
</ beans>
Spring 的 p命名空间

p命名空间是对IoC/DI的简化操作,使用p命名空间,可以更加方便的完成bean的配置以及bean之间的依赖注入

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


    <bean id="student" class="com.liner.spring.springIOC.Student" p:id="1" p:age="23" p:name="王五" p:address-ref=" address"/>
    <bean id=" address" class="com.liner.spring.springIOC.Address" p:id="2" p:name="科技路"/>

</beans>

Spring 的工厂方法

loC通过工厂模式创建bean的方式有两种:

  • 静态工厂方法
//创建一个Car的实体类
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Car {
    private Long id;
    private String name;
}
//创建一个静态工厂
public class StaticCarFactory {
    private static Map<Long, Car> carMap;
    static {
        carMap = new HashMap<Long,Car>();
        carMap.put(1L,new Car(1L,"长城"));
        carMap.put(2L,new Car(2L,"长安"));
    }

    public static Car getCar(Long id) {
        return carMap.get(id);
    }
}
<!-- 配置静态工厂处理Car-->
<bean id="car" class="com.liner.spring.springIOC.factory.StaticCarFactory" factory-method="getCar">
    <constructor-arg value="2"></constructor-arg>
</bean>
  • 实例工厂方法
//创建一个Car的实体类
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Car {
    private Long id;
    private String name;
}
//创建一个实例工厂
public class InstanceCarFactory {
    private Map<Long, Car> carMap;

    public InstanceCarFactory() {
        carMap = new HashMap<Long, Car>();
        carMap.put(1L,new Car(1L,"长城"));
        carMap.put(2L,new Car(2L,"长安"));
    }

    public Car getCar(Long id) {
        return carMap.get(id);
    }
}
<!--    配置实例工厂 bean-->
<bean id="carFactory" class="com.liner.spring.springIOC.factory.InstanceCarFactory" />
<!--    配置实例工厂创建 Car-->
<bean id="car" factory-bean="carFactory" factory-method="getCar">
    <constructor-arg value="2"></constructor-arg>
</bean>
loC自动装载(Autowire)

loC负责创建对象,DI负责完成对象的依赖注入,通过配置property标签的 ref属性来完成,同时Spring 提供了另外一种更加简便的依赖注入方式∶自动装载,不需要手动配置property,loC容器会自动选择bean完成注入。

自动装载有两种方式:

  • byName:通过属性名自动装载
<bean id="car" class="com.liner.spring.springIOC.entity.Car">
    <property name="id" value="1"></property>
    <property name="name" value="宝马"></property>
</bean>
<bean id="person" class="com.liner.spring.springIOC.entity.Person" autowire="byName">
	<property name="id" value="3"></property>
	<property name="name" value="小亮"></property>
</bean>
  • byType:通过属性的数据类型自动装载
<bean id="cars" class="com.liner.spring.springIOC.entity.Car">
    <property name="id" value="1"></property>
    <property name="name" value="宝马"></property>
</bean>
<bean id="person" class="com.liner.spring.springIOC.entity.Person" autowire="byType">
	<property name="id" value="3"></property>
	<property name="name" value="小亮"></property>
</bean>

注意:使用byType需要注意,如果==同时存在两个及以上的符合条件==的bean时,自动装载会抛出异常。

SpringAOP

AOP: Aspect Oriented Programming 面向切面编程

AOP的优点
  • 降低模块之间的耦合度
  • 使系统容易扩展
  • 更好的代码复用
  • 非业务代码更加集中,不分散,便于统一管理业务代码更简洁纯粹,没有其他代码的影响
  • 将复杂的需求分解出不同方面,将散布在系统中的公共功能集中解决

AOP是对面向对象编程的一个补充,在运行时,动态地将代码切入到类的指定方法、指定位置上的编程思想就是面向切面编程。将不同方法的同一个位置抽象成一个切面对象,对该切面对象进行编程就是AOP。

如何使用AOP
  • 创建Maven工程,pom.xml添加依赖
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-aop</artifactId>
    <version>5.3.19</version>
</dependency>

<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-aspects</artifactId>
    <version>5.3.19</version>
</dependency>

<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-context</artifactId>
    <version>5.3.19</version>
</dependency>
  • 创建一个计算器接口,实现加减乘除四个方法
public interface Cal {
    public int add(int num1,int num2);//加法
    public int sub(int num1,int num2);//减法
    public int mul(int num1,int num2);//乘法
    public int div(int num1,int num2);//除法
}
  • 创建接口实现类
public class CalImpl implements Cal {
    @Override
    public int add(int num1, int num2) {
        System.out.println("add方法的参数: num1:" + num1 + " ,num2:" + num2);
        int result = num1 + num2;
        System.out.println("add方法的结果是:" + result);
        return result;
    }

    @Override
    public int sub(int num1, int num2) {
        System.out.println("sub方法的参数: num1:" + num1 + " ,num2:" + num2);
        int result = num1 - num2;
        System.out.println("sub方法的结果是:" + result);
        return result;
    }

    @Override
    public int mul(int num1, int num2) {
        System.out.println("mul方法的参数: num1:" + num1 + " ,num2:" + num2);
        int result = num1 * num2;
        System.out.println("mul方法的结果是:" + result);
        return result;
    }

    @Override
    public int div(int num1, int num2) {
        System.out.println("div方法的参数: num1:" + num1 + " ,num2:" + num2);
        int result = num1 / num2;
        System.out.println("div方法的结果是:" + result);
        return result;
    }
}

上述代码中,日志信息和业务逻辑的耦合性很高,不利于系统的维护,使用AOP可以进行优化。

如何来实现AOP
  • 使用**动态代理**的方式来实现。

给业务代码找一个代理,打印日志信息的工作交个代理来做,这样业务代码只需 关注自身的业务即可。

/**
 * 记录委托对象,创建代理对象。
 * 代理对象的创建需要 实现 委托对象的全部接口 (知道所有接口 意味着 知道了所有的方法)
 * 根据 委托对象的所有接口 动态创建 一个 代理类
 * 有了代理类,也就有了代理对象       相当于把委托对象的所有功能全部复制给了代理对象
 */
public class MyInvocationHandler implements InvocationHandler {

    //接收委托对象
    private Object object = null;

    //返回代理对象
    public Object bind(Object object){
        this.object = object;
        //返回一个代理实例 即 代理对象
        /**
         * 代理对象的创建需要类,类是动态生成的,
         * 在程序运行时需要向JVM中将动态生成的类添加进去
         * 添加 需要 类加载器:
         * object.getClass() 获取委托对象的 运行时类,即动态生成的类
         * object.getClass().getClassLoader()   : 获取类加载器
         *
         * 反射机制
         *
         * 有了类加载器就可生成一个动态的类
         * 动态的类生成的特点:委托类的全部功能 代理类都必须全部实现
         * Java中通过接口描述功能   全部实现 委托类的 全部功能 即 实现 委托对象的全部接口
         * object.getClass().getInterfaces()    :获取委托对象的 运行时类 的 所有接口
         *
         * 将委托对象的全部接口 交给 类加载器 ,类加载器就可根据接口 动态创建一个 功能和委托类 一样的 代理类,即动态代理类
         *
         * this     : 通过当前的 MyInvocationHandler 创建 动态代理对象
         */
        //通过该方法可获得 动态代理对象
        return Proxy.newProxyInstance(object.getClass().getClassLoader(), object.getClass().getInterfaces(),this);
    }


    /**
     *
     * @param proxy 当前代理对象
     * @param method 代理对象的方法
     * @param args  代理对象的参数
     * @return
     * @throws Throwable
     */
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println( method.getName() + "方法的参数: " + Arrays.toString(args));
        //当前目标方法 通过反射机制 调用委托对象的方法
        Object result = method.invoke(this.object, args);//让委托对象调用自己的方法
        System.out.println( method.getName() + "方法的结果是: " + result);

        return result;
    }
}
//创建接口实现类
public class CalImpl implements Cal {
    @Override
    public int add(int num1, int num2) {
        return num1 + num2;
    }

    @Override
    public int sub(int num1, int num2) {
        return num1 - num2;
    }

    @Override
    public int mul(int num1, int num2) {
       return num1 * num2;
    }

    @Override
    public int div(int num1, int num2) {
        return num1 / num2;
    }
}
public static void main(String[] args) {
    //委托对象 cal
    Cal cal = new CalImpl();
    MyInvocationHandler myInvocationHandler = new MyInvocationHandler();
    //bind() 将委托对象传进去  返回 动态代理对象  proxy
    Cal proxy = (Cal) myInvocationHandler.bind(cal);
    proxy.add(1,2);  //实际进入的是  MyInvocationHandler 的 invoke()里
    proxy.sub(2,2);
    proxy.mul(3,2);
    proxy.div(2,2);

}

以上是通过**动态代理实现AOP**的过程,比较复杂,不好理解,Spring框架对AOP进行了封装

  • 使用Spring框架可以用面向对象的思想来实现AOP。

Spring框架中**不需要创建InvocationHandler**,只需要==创建一个切面对象==,将所有的非业务代码在切面对象中完成即可,Spring框架底层会自动根据切面类以及目标类生成一个代理对象。

LoggerAspect

@Aspect //给它个切面的功能
@Component  //让IoC管理它
public class LoggerAspect {}

    /**
     * @param joinPoint 连接点
     */
    //通过 execution 表达式去关联
       @Before("execution(public int com.liner.spring.springAOP.utils.impl.CalImpl.*(..))")
    public void before(JoinPoint joinPoint){
        //获取方法名
        String name = joinPoint.getSignature().getName();
        //获取参数
        String args = Arrays.toString(joinPoint.getArgs());
        System.out.println( name + "方法的参数: " + args);
    }

    @After("execution(public int com.liner.spring.springAOP.utils.impl.CalImpl.*(..))")
    public void After(JoinPoint joinPoint){
        //获取方法名
        String name = joinPoint.getSignature().getName();

        System.out.println(name + "方法执行完毕....." );
    }


    @AfterReturning(value = "execution(public int com.liner.spring.springAOP.utils.impl.CalImpl.*(..))",returning = "result")
    public void AfterReturning(JoinPoint joinPoint, Object result){
        //获取方法名
        String name = joinPoint.getSignature().getName();
        //获取结果
        System.out.println(name + "方法执行结果为:" + result);
    }

     @AfterThrowing(value = "execution(public int com.liner.spring.springAOP.utils.impl.CalImpl.*(..))", throwing = "exception")
        public void AfterThrowing(JoinPoint joinPoint, Exception exception){
            //获取方法名
            String name = joinPoint.getSignature().getName();
            //获取结果
            System.out.println(name + "方法执行抛出的异常为:" + exception);
    }

}

LoggerAspect类定义处添加的两个注解:

  • Aspect:表示该类是切面类。

  • component:将该类的对象注入到loC容器。

具体方法处添加的注解:

@Before:表示方法执行的具体位置和时机。

Callmpl也需要添加@Component,交给loC容器来管理。

@Component
public class CalImpl implements Cal {
    @Override
    public int add(int num1, int num2) {
        return num1 + num2;
    }

    @Override
    public int sub(int num1, int num2) {
        return num1 - num2;
    }

    @Override
    public int mul(int num1, int num2) {
       return num1 * num2;
    }

    @Override
    public int div(int num1, int num2) {
        return num1 / num2;
    }
}

在Spring.xml中配置AOP

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


    <!--    自动扫描    -->
    <context:component-scan base-package="com.liner.spring.springAOP"></context:component-scan>
    <!--    使Aspect生效,为目标类自动生成代理对象   -->
    <aop:aspectj-autoproxy></aop:aspectj-autoproxy>

</beans>

context:component-scancom.liner.spring.springAOP包中的所有类进行扫描,如果该类同时添加了@Component,则将该类扫描到loC容器中,即loC管理它的对象。
aop:aspectj-autoproxy 让Spring框架结合切面类和目标类自动生成动态代理对象。

  • 切面:横切关注点被模块化的抽象对象。
  • 通知:切面对象完成的工作。
  • 目标:被通知的对象,即被横切的对象。·
  • 代理:切面、通知、目标混合之后的对象。
  • 连接点:通知要插入业务代码的具体位置。
  • 切点:AOP通过切点定位到连接点。

SpringMVC

Spring MVC概述

SpringMVC是目前主流的实现MVC 设计模式的框架,是Spring框架的一个分支产品,以 Spring loC容器为基础,并利用容器的特性来简化它的配置。相当于Spring框架的一个子模块,无需整合,开发起来更加便捷。可以很好的和Spring 结合起来进行开发,是Java Web开发者必须要掌握的框架。

什么是MVC设计模式

将应用程序分为Controller、Model、View三层,Controller接收客户端请求,调用Model生成业务数据,传递给View。
Spring MVC就是对这套流程的封装,屏蔽了很多底层代码,开放出接口,让开发者可以更加轻松、便捷地完成基于MVC模式的Web开发。

人机交互
封装模型数据
调用业务模型
数据库存取,获取明细数据
View
Controller
Model
DataBase

Spring MVC的特点

  • 清晰地角色划分
  • 灵活的配置功能
  • 提供了大量的控制器接口和实现类
  • 分离View层的实现
  • 国际化支持
  • 面向接口编程

Spring MVC核心组件

  • DispatcherServlet前置控制器,是整个流程控制的核心,控制其他组件的执行,进行统一调度
    降低组件之间的耦合性,相当于总指挥。
  • Handler处理器,完成具体的业务逻辑,相当于Servlet 或 Action。
  • HandlerMapping处理器映射器, DispatcherServlet 接收到请求之后,通过 HandlerMapping将不同的请求映射到不同的Handler。
  • HandlerInterceptor处理器拦截器,是一个接口,如果需要完成一些拦截处理,可以实现该接口。
  • HandlerExecutionChain处理器执行链,包括两部分内容:Handler和HandlerInterceptor(系统会有一个默认HandlerInterceptor,如果需要额外设置拦截,可以添加拦截器)。
  • HandlerAdapter处理器适配器,Handler执行业务方法之前,需要进行一系列的操作,包括表单数据的验证、数据类型的转换、将表单数据封装到JavaBean等,这些操作都是由HandlerApater来完成开发者只需将注意力集中业务逻辑的处理上,DispatcherServlet通过HandlerAdapter执行不同的Handler。
  • ModelAndView:装载了模型数据和视图信息,作为Handler的处理结果,返回给DispatcherServlet.。
  • ViewResolver视图解析器,DispatcheServlet通过它将逻辑视图解析为物理视图,最终将渲染结果响应给客户端。
1. 发送请求
2. 获取 Handler
3. 返回 HandlerExecutionChain
10. 得到响应
4. 请求 HandlerAdapter
5. 执行 Handler
6. 返回 ModelAndView
7. 返回 ModelAndView
8. 解析 ModelAndView
9. 返回 View
客户端
DispatcherServlet
HandlerMapping
HandlerAdapter
Handler
ViewResolver

Spring MVC工作流程

  • 客户端请求被 DispatcherServlet接收。
  • 根据HandlerMapping映射到Handler。
  • 生成Handler和 Handlerlnterceptor。
  • Handler和HandlerInterceptor 以 HandlerExecutionChain 的形式一并返回给DisptacherServlet。
  • DispatcherServlet通过HandlerAdapter调用Handler的方法完成业务逻辑处理。
  • Handler返回一个ModelAndView给 DispatcherServlet。
  • DispatcherServlet将获取的 ModelAndView对象传给ViewResolver视图解析器,将逻辑视图解析为物理视图View。
  • ViewResovler返回一个View给DispatcherServlet。
  • DispatcherServlet根据View进行视图渲染(将模型数据Model填充到视图View中)。
  • DispatcherServlet将渲染后的结果响应给客户端。

Spring MVC流程非常复杂,实际开发中很简单,因为大部分的组件不需要开发者创建、管理,只需要通过配置文件的方式完成配置即可,真正需要开发者进行处理的只有Handler 、 View。

如何使用SpringMVC

  • 创建Maven工程,pom.xml
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-webmvc</artifactId>
    <version>5.3.19</version>
</dependency>
  • 在web.xml中配置 DispatcherServlet
<!DOCTYPE web-app PUBLIC
 "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
 "http://java.sun.com/dtd/web-app_2_3.dtd" >

<web-app>
  <display-name>Archetype Created Web Application</display-name>

  <servlet>
    <servlet-name>dispatcherServlet</servlet-name>
    <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
    <init-param>
      <param-name>contextConfigLocation</param-name>
      <param-value>classpath:springmvc.xml</param-value>
    </init-param>
  </servlet>
  <servlet-mapping>
    <servlet-name>dispatcherServlet</servlet-name>
    <url-pattern>/</url-pattern>
  </servlet-mapping>
</web-app>
  • springmvc.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       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:component-scan base-package="com.liner"/>
<!--    配置视图解析器-->
    <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
        <property name="prefix" value="/"/>
        <property name="suffix" value=".jsp"/>
    </bean>
</beans>
  • 创建Handler
@Controller
public class HelloHandler {

  @RequestMapping("/index")
  public String index() {
    System.out.println("打个招呼...Hello!");
    return "index";
  }
}

SpringMVC注解

  • @RequestMapping
    Spring MVC通过 @RequestMapping 注解将URL请求与业务方法进行映射,在Handler 的类定义处以及方法定义处都可以添加@RequestMapping,在类定义处添加,相当于客户端多了一层访问路径。

  • @Controller
    @Controller在类定义处添加,将该类交个loC容器来管理(结合springmvc.xml 的自动扫描配置使用),同时使其成为一个控制器,可以接收客户端请求

  • @RequestParam

    @RequestParam,当请求参数和方法形参不一致时,完成对HTTP请求参数与业务方法形参的映射.

  • @PathVariable

    @PathVariable,使用REST风格时,完成对HTTP请求参数与业务方法形参的映射.

  • @CookieValue

    @CookieValue,通过映射可以直接在业务方法中获取Cookie 的值。

@RequestMapping相关参数
  1. value:指定URL请求的实际地址,是@RequestMapping的默认值。

    @RequestMapping("/index")
    //or
    @RequestMapping(value = "/index")
    
  2. method:指定请求的method类型,GET、POST、PUT、DELET。

    //表示方法只能接收GET请求。
    @RequestMapping(value = "/index",method = RequestMethod.GET)
    
  3. params:指定请求中必须包含某些参数,否则无法调用该方法。

    //表示请求中必须包含id 和name两个参数,同时id的值必须是1
    @RequestMapping(value = "/index",method = RequestMethod.GET,params = {"id=1","name"})
    

关于参数绑定,在形参列表中通过添加@RequestParam注解完成HTTP请求参数与业务方法形参的映射。

//表示将请求参数 name和 id分别赋给了形参name和 age,同时自动完成了数据类型转换,将“1"”转为了int类型的1,再赋给age
//这些工作都是由HandlerAdapter来完成的。
@RequestMapping(value = "/index",method = RequestMethod.GET,params = {"id=1","name"})
public String index(String name, @RequestParam("id") int age) {
	System.out.println("打个招呼...Hello!");
	System.out.println("我叫:" + name + "今年" + age + "岁了");
	return "index";
}

Spring MVC 也支持 RESTful风格的URL。

@RequestMapping(value = "/index",method = RequestMethod.GET,params = {"id=1","name"})
//也可写成
@RequestMapping("/index/{id}/{name}")
@RequestMapping("/index/{id}/{name}")
public String index(String name, @PathVariable("id") int age) {
	System.out.println("打个招呼...Hello!");
	System.out.println("我叫:" + name + "今年" + age + "岁了");
	return "index";
}
映射Cookie
@RequestMapping("/cookie")
public String cookie(@CookieValue(value = "JSESSIONID") String sessionId) {
    System.out.println(sessionId);
    return "index";
}
使用JavaBean 绑定参数

Spring MVC会根据请求参数名和JavaBean属性名进行自动匹配,自动为对象填充属性值,同时支持级联属性。

@Data
public class Address {
    private String value;

}
@Data
public class User {
    private long id;
    private String name;
    private Address address;
}
@PostMapping("/regist")
public String regist(User user){
    System.out.println(user);
    return "index";
}
<form action="/regist" method="post">
    用户id:<input type="text" name="id"/><br/>
    用户名:<input type="text" name="name"/><br/>
    住址:<input type="text" name="address.value"/><br/>
    <input type="submit" value="注册">
</form>

如果出现中文乱码问题,只需在web.xml添加Spring MVC自带的过滤器即可。

<filter>
    <filter-name>encodingFilter</filter-name>
    <filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
    <init-param>
        <param-name>encoding</param-name>
        <param-value>UTF-8</param-value>
    </init-param>
</filter>
<filter-mapping>
    <filter-name>encodingFilter</filter-name>
    <url-pattern>/*</url-pattern>
</filter-mapping>
jsp的转发和重定向

Spring MVC默认是以转发的形式响应JSP。

  • 转发
@RequestMapping("/forward")
public String forward() {
	return "forward:/index.jsp" ;// =return "index" ;
}

2、重定向

@RequestMapping("/redirect")
public String redirect() {
	return "redirect:/index.jsp" ;
}

Spring MVC数据绑定

数据绑定:在后端的业务方法中直接获取客户端HTTP请求中的参数,将请求参数映射到业务方法的形参中,Spring MVC中数据绑定的工作是由 HandlerAdapter来完成的。

基本数据类型
@RequestMapping("/baseType")
@ResponseBody
public String baseType(int id){
	return id+"";
}

**@ResponseBody**表示Spring MVC会直接将业务方法的返回值响应给客户端,如果不加 @ResponseBody注解,Spring MVC会将业务方法的返回值传递给DispatcherServlet,再由DisptacherServlet调用ViewResolver对返回值进行解析,映射到一个JSP资源。

包装类
/**
* @RequestParam value = "num":将HTTP请求中名为num的参数赋给形参id。 
* requried:设置num是否为必填项, true表示必填,false表示非必填,可省略。
* defaultValue = “0":如果HTTP请求中没有num参数,默认值为0.
*/
@RequestMapping("/packageType")
@ResponseBody
public String packageType(@RequestParam(value = "num",required = false,defaultValue = "0") Integer id){
    return id+"";
}
//包装类可以接收null,当HTTP请求没有参数时,使用包装类定义形参的数据类型,程序不会抛出异常。

二者区别基本数据类型不能接收null值,包装类可以接收null值

数组
@RestController
@RequestMapping( " /data" )
public class DataBindHandler {
   @RequestMapping("/array")
    public String array(String[] name){
        String str = Arrays.toString(name);
        return str;
    }	
}
  • @RestController 表示该控制器会直接将业务方法的返回值响应给客户端,不进行视图解析。
  • Controller 表示该控制器的每一个业务方法的返回值都会交给视图解析器进行解析,如果只需要将数据响应给客户端,而不需要进行视图解析,则需要在对应的业务方法定义处添加@ResponseBody
List

Spring MVC不支持List类型的直接转换,需要对List集合进行包装。

集合封装类

@Data
public class UserList {
	private List<User> users;
}

JSP

<body>
	<form action="/list" method="post">
        用户1编号:<input type="text" name="users[0].id"/><br/>
        用户1名称:<input type="text" name="users[0].name"/><br/>
        用户2编号:<input type="text" name="users[1].id"/><br/>
        用户2名称:<input type="text" name="users[1].name"/><br/>
        用户3编号:<input type="text" name="users[2].id"/><br/>
        用户3名称:<input type="text" name="users[2].name"/><br/>
    <input type="submit" value="提交"/>
    </form>
</ body>

方法

@RequestMapping("/list")
public String list(UserList userList){
	StringBuffer str = new StringBuffer( );
	for(User user:userList.getUsers()){
		str.append (user);
	}
return str.toString();
}

处理@ResponseBody中文乱码,在springmvc.xml中配置消息转换器。

<mvc:annotation-driven>
    <!--      消息转换器-->
    <mvc:message-converters register-defaults="true">
        <bean class="org.springframework.http.converter.StringHttpMessageConverter">
            <property name="supportedMediaTypes" value="text/html;charset=UTF-8"/>
        </bean>
    </mvc:message-converters>
</mvc:annotation-driven>
Map

自定义封装类

@Data
public class UserMap {
	private Map<String,User> users;
}

JSP

<body>
	<form action="/list" method="post">
        用户1编号:<input type="text" name="users['a'].id"/><br/>
        用户1名称:<input type="text" name="users['a'].name"/><br/>
        用户2编号:<input type="text" name="users['b'].id"/><br/>
        用户2名称:<input type="text" name="users['b'].name"/><br/>
        用户3编号:<input type="text" name="users['c'].id"/><br/>
        用户3名称:<input type="text" name="users['c'].name"/><br/>
    <input type="submit" value="提交"/>
    </form>
</ body>

方法

@RequestMapping("/map")
public String list(UserMap userMap){
    StringBuffer str = new StringBuffer( );
    for(String key : userMap.getUsers().keySet()){
        User user = userMap.getUsers().get(key);
        str.append(user);
    }
    return str.toString();
}
JSON

客户端发生JSON格式的数据,直接通过Spring MVC绑定到业务方法的形参中。

处理Spring MVC 无法加载静态资源,在web.xml中添加配置即可。

<servlet-mapping>
    <servlet-name>default</servlet-name>
    <url-pattern>*.js</url-pattern>
</servlet-mapping>

JSP

<head>
    <title>JSON</title>
    <script type="text/javascript" src="js/jquery-3.3.1.min.js"></script>
    <script type="text/javascript">
        $(function(){
            var user = {
                "id":1,
                "name":"张三"
            };
            $.ajax({
                url: "/json",
                data:JSON.stringify(user),
                type: "POST",
                contentType: "application/json;charset=UTF-8",
                dataType: "JSON"",
                success: function(data){
                    alter(data.id+"--"+data.name);
                }
            })
          });
    </script>
</head>

方法

@RequestMapping("/json")
public User json(@RequestBody User user){
  	user.setId(1);
    user.setName("小明");
    return user;
}

Spring MVC中的JSON和JavaBean的转换需要借助于fastjson,pom.xml引入相关依赖。

<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>fastjson</artifactId>
    <version>1.2.80</version>
</dependency>

springmvc.xml添加fastjson 配置。

<mvc:annotation-driven>
    <!-- 消息转换器-->
    <mvc:message-converters register-defaults="true">
        <bean class="org.springframework.http.converter.StringHttpMessageConverter">
            <property name="supportedMediaTypes" value="text/html;charset=UTF-8"/>
        </bean>
        <!-- 配置fastjson-->
        <bean class="com.alibaba.fastjson.support.spring.FastJsonHttpMessageConverter4"/>
    </mvc:message-converters>
</mvc:annotation-driven>

SpringMVC 模型数据解析

JSP 四大作用域对应的内置对象:pageContext、request、session、application
模型数据的绑定是由ViewResolver来完成的,实际开发中,我们需要先添加模型数据,再交给ViewResolver来绑定。

Spring MVC提供了以下几种方式添加模型数据:

  • Map

  • Model

  • ModelAndView

  • @SessionAttribute

  • @ModelAttribute

将模型数据绑定到request对象

Map
@RequestMapping("/map")
public String map(Map<String,User> map){
    User user = new User();
    user.setId(1L);
    user.setName("小亮");
    map.put("user",user);
    return "view" ;
}

JSP

<body>
    ${requestScope.user}
</body>
Model
@RequestMapping("/model")
public String model(Model model){
    User user = new User();
    user.setId(2L);
    user.setName("小王");
    model.addAttribute("user",user);
    return "view" ;
}
ModelAndView
@RequestMapping("/modelAndView")
public ModelAndView modelAndView(){
    User user = new User();
    user.setId(3L);
    user.setName("小赵");
   	ModelAndView modelAndView = new ModelAndView();
    modelAndView.addObject("user",user);
    modelAndView.setViewName("view");
    return modelAndView ;
}
//or
@RequestMapping("/modelAndView2")
public ModelAndView modelAndView2(){
    User user = new User();
    user.setId(3L);
    user.setName("小赵");
   	ModelAndView modelAndView = new ModelAndView();
    modelAndView.addObject("user",user);
    View view = new InternalResourceView("/view.jsp");
    modelAndView.setView(view);
    return modelAndView ;
}
//or
@RequestMapping("/modelAndView3")
public ModelAndView modelAndView3(){
    User user = new User();
    user.setId(3L);
    user.setName("小赵");
   	ModelAndView modelAndView = new ModelAndView("view");
    modelAndView.addObject("user",user);
    return modelAndView ;
}
//or
@RequestMapping("/modelAndView4")
public ModelAndView modelAndView4(){
    User user = new User();
    user.setId(3L);
    user.setName("小赵");
    View view = new InternalResourceView("/view.jsp");
   	ModelAndView modelAndView = new ModelAndView(view);
    modelAndView.addObject("user",user);
    return modelAndView ;
}
//or
@RequestMapping("/modelAndView5")
public ModelAndView modelAndView5(){
    User user = new User();
    user.setId(3L);
    user.setName("小赵");
    Map<String,User> map = new HashMap<>();
    map.put("user",user);
   	ModelAndView modelAndView = new ModelAndView("view",map);
    return modelAndView ;
}
//or
@RequestMapping("/modelAndView6")
public ModelAndView modelAndView6(){
    User user = new User();
    user.setId(3L);
    user.setName("小赵");
    Map<String,User> map = new HashMap<>();
    map.put("user",user);
    View view = new InternalResourceView("/view.jsp");
   	ModelAndView modelAndView = new ModelAndView(view,map);
    return modelAndView ;
}
//or
@RequestMapping("/modelAndView7")
public ModelAndView modelAndView7(){
    User user = new User();
    user.setId(3L);
    user.setName("小赵");
   	ModelAndView modelAndView = new ModelAndView("view","user",user);
    return modelAndView ;
}
//or
@RequestMapping("/modelAndView8")
public ModelAndView modelAndView8(){
    User user = new User();
    user.setId(3L);
    user.setName("小赵");
    View view = new InternalResourceView("/view.jsp");
   	ModelAndView modelAndView = new ModelAndView(view,"user",user);
    return modelAndView ;
}
HttpServletRequest
@RequestMapping("/request")
public String request(HttpServletRequest request){
    User user = new User();
    user.setId(4L);
    user.setName("小刚");
   	request.setAttribute("user",user);
    return "view";
}
@ModelAttribute
  • 定义一个方法,该方法专门用来返回要填充到模型数据中的对象。
@ModelAttribute
public User getUser(){
    User user = new User();
    user.setId(5L);
    user.setName("小法");
    return user;
}
@ModelAttribute
public void getUser(Map<String,User> map){
    User user = new User();
    user.setId(5L);
    user.setName("小法");
    map.put("user",user);
}
@ModelAttribute
public void getUser(Mpdel model){
    User user = new User();
    user.setId(5L);
    user.setName("小法");
    model.addAttribute("user",user);
}
  • 业务方法中无需再处理模型数据,只需返回视图即可。
@RequestMapping( "/modelAttribute")
public String modelAttribute(){
    return "view";
}

将模型数据绑定到session对象

直接使用原生的Servlet API
@RequestMapping("/session")
public String session(HttpServletRequest request){
    Httpsession session = request.getsession();
    User user = new User();
    user.setId(1L);
    user.setName("张飞");
    session.setAttribute("user",user);
    return "view" ;
}
@RequestMapping ("/session2")
public String session2 (HttpSession session){
    User user = new User ();
    user.setId(1L);
    user.setName("张飞");
    session.setAttribute("user",user);
    return "view" ;
}
@SessionAttribute
@SessionAttributes(value = {"user","address"})
public class ViewHandler {
}

对于ViewHandler中的所有业务方法,只要向request中添加了key = “user”、key = "address” 的对象时,Spring MVC会自动将该数据添加到session中,保存key不变。

@SessionAttributes(types = {User.class,Address.class})
public class ViewHandler {
}

对于ViewHandler中的所有业务方法,只要向request中添加了数据类型是User、Address的对象时,Spring MVC会自动将该数据添加到session中,保存key 不变。

将模型数据绑定到application对象

@RequestMapping("/application")
public String application(HttpServletRequest request){
    ServletContext application = request.getServletContext();
    User user = new User();
    user.setId(1L);
    user.setName("张良");
    application.setAttribute("user" user);
    return "view";
}

Spring MVC自定义数据转换器

数据转换器是指将客户端HTTP请求中的参数转换为业务方法中定义的形参,自定义表示开发者可以自主设计转换的方式,HandlerApdter已经提供了通用的转换,String 转 int,String 转 double,表单数据的封装等,但是在特殊的业务场景下,HandlerAdapter无法进行转换,就需要开发者自定义转换器。

客户端输入String类型的数据"2022-06-05",自定义转换器将该数据转为Date类型的对象。

  • 创建DateConverter转换器,实现Conveter接口。
public class DateConverter implements Converter<String, Date> {
    private String pattern;

    public DateConverter(String pattern) {
        this.pattern = pattern;
    }

    @Override
    public Date convert(String s) {
        SimpleDateFormat simpleDateFormat = new SimpleDateFormat(this.pattern);
        Date date = null;
        try {
            date = simpleDateFormat.parse(s);
        } catch (ParseException e) {
            e.printStackTrace();
        }
        return date;
    }
}
  • 在springmvc.xml配置转换器
<!--    配置自定义转换器-->
<bean class="org.springframework.context.support.ConversionServiceFactoryBean" id="conversionService">
    <property name="converters">
        <list>
            <bean class="com.liner.springmvc.converter.DateConverter">
                <constructor-arg type="java.lang.String" value="yyyy-MM-dd"/>
            </bean>
        </list>
    </property>

</bean>

<mvc:annotation-driven conversion-service="conversionService">
    <!--      消息转换器-->
    <mvc:message-converters register-defaults="true">
        <bean class="org.springframework.http.converter.StringHttpMessageConverter">
            <property name="supportedMediaTypes" value="text/html;charset=UTF-8"/>
        </bean>
        <!--          配置fastjson-->
        <bean class="com.alibaba.fastjson.support.spring.FastJsonHttpMessageConverter4"/>
    </mvc:message-converters>
</mvc:annotation-driven>
  • JSP
<body>
    <form action="/converter/date" method="post">
        <input type="text" name="date">(yyyy-MM-dd)</br>
        <input type="submit" value="提交">
    </form>
</body>
  • Handler
@RestController
@RequestMapping("/converter")
public class ConverterHandler {

    @RequestMapping("/date")
    public String Date(Date date){
        return date.toString();
    }
}

客户端输入String类型的数据"2022-06-05",自定义转换器将该数据转为Student类。

  • 创建StudentConverter转换器,实现Conveter接口。
public class StudentConverter implements Converter<String,student>
    @Override
    public Student convert(String s){
        String[] args = s.split("-");
        Student student = new Student() ;
        student.setId(Long.parseLong(args[0]));
        student.setName(args[1]);
        student.setAge(Integer.parseInt (args[2]));
        return student;
	}
}
  • 在springmvc.xml配置转换器
	<!--    配置自定义转换器-->
    <bean class="org.springframework.context.support.ConversionServiceFactoryBean" id="conversionService">
        <property name="converters">
            <list>
                <bean class="com.liner.springmvc.converter.DateConverter">
                    <constructor-arg type="java.lang.String" value="yyyy-MM-dd"/>
                </bean>
                <bean class="com.liner.springmvc.converter.StudentConverter"/>
            </list>
        </property>

    </bean>

    <mvc:annotation-driven conversion-service="conversionService">
        <!--  消息转换器-->
        <mvc:message-converters register-defaults="true">
            <bean class="org.springframework.http.converter.StringHttpMessageConverter">
                <property name="supportedMediaTypes" value="text/html;charset=UTF-8"/>
            </bean>
            <!--  配置fastjson-->
            <bean class="com.alibaba.fastjson.support.spring.FastJsonHttpMessageConverter4"/>
        </mvc:message-converters>
    </mvc:annotation-driven>
  • JSP
<body>
    <form action="/converter/student" method="post">
        请输入学生信息:<input type="text" name="student">(id-name-age)</br>
        <input type="submit" value="提交">
    </form>
</body>
  • Handler
@RequestMapping("/student")
public String student(Student student){
    return student.toString();
}

SpringMVC REST

REST

Representational State Transfer,资源表现层状态转换,是目前比较主流的一种互联网软件架构,它结构清晰、标准规范、易于理解、便于扩展。

  • 资源 (Resource)

网络上的一个实体,或者说网络中存在的一个具体信息,一段文本、一张图片、一首歌曲、一段视频等等,总之就是一个具体的存在。可以用一个URI(统―资源定位符)指向它,每个资源都有对应的一个特定的URI,要获取该资源时,只需要访问对应的URI即可。

  • 表现层 (Representation)

资源具体呈现出来的形式,比如文本可以用txt格式表示,也可以用HTML、XML、JSON等格式来表示。

  • 状态转换 (State Transfer)

客户端如果希望操作服务器中的某个资源,就需要通过某种方式让服务端发生状态转换,而这种转换是建立在表现层之上的,所有叫做"表现层状态转换"。

特点
  • URL更加简洁。
  • 有利于不同系统之间的资源共享,只需要遵守一定的规范,不需要进行其他配置即可实现资源共享。
如何使用

REST具体操作就是HTTP协议中四个表示操作方式的动词分别对应CRUD基本操作。

  • GET:用来表示获取资源。
  • POST :用来表示新建资源。
  • PUT:用来表示修改资源。
  • DELETE:用来表示删除资源。

SpringMVC文件上传和下载

单文件上传

底层是使用Apache fileupload组件完成上传,Spring MVC对这种方式进行了封装。

  • pom.xml
<dependency>
    <groupId>commons-io</groupId>
    <artifactId>commons-io</artifactId>
    <version>2.11.0</version>
</dependency>

<dependency>
    <groupId>commons-fileupload</groupId>
    <artifactId>commons-fileupload</artifactId>
    <version>1.4</version>
</dependency>
  • JSP
<%--
    1、input的type设置为file。
    2、form的 method设置为post (get请求只能将文件名传给服务器)
    3、from的enctype设置为multipart-form-data (如果不设置只能将文件名传给服务器)
--%>
<form action="/file/upload" method="post" enctype="multipart/form-data">
    <input type="file" name="img"></br>
<input type="submit" value="提交">
</form>
  • Handler
@RestController
@RequestMapping("/file")
public class FileHandler {

  @PostMapping("upload")
  public String upload(MultipartFile img, HttpServletRequest request) {
    System.out.println(img);
    if (img.getSize() > 0) {
      //获取保存上传文件的file路径
      String path = request.getServletContext().getRealPath("file"); 
      //获取上传的文件名
      String name = img.getOriginalFilename();
      File file = new File(path, name);
      try {
        img.transferTo(file);
        //保存上传之后的文件路径
        request.setAttribute("path", "/file/" + name);
      } catch (IOException e) {
        e.printStackTrace();
      }
    }
    return "upload";
  }
}
  • springmvc.xml
<!--配置上传组件-->
<bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver"/>
  • web.xml添加如下配置,否则客户端无法访问png (注意后缀,也可改为其他图片后缀)
<servlet-mapping>
    <servlet-name>default</servlet-name><url-pattern>*.png</url-pattern>
</servlet-mapping>

多文件上传

  • pom.xml
<dependency>
    <groupId>commons-io</groupId>
    <artifactId>commons-io</artifactId>
    <version>2.11.0</version>
</dependency>

<dependency>
    <groupId>commons-fileupload</groupId>
    <artifactId>commons-fileupload</artifactId>
    <version>1.4</version>
</dependency>
  • JSP
<form action="/file/uploads" method="post" enctype="multipart/form-data">
    file1:<input type="file" name="imgs"/><br/>
    file2:<input type="file" name="imgs"/><br/>
    file3:<input type="file" name="imgs"/><br/>
    <input type="submit" value="上传"/>
</form>
<c:forEach items="${files}" var="file">
    <img src="${file}" width="300px">
</c:forEach>
  • Handler
@PostMapping("uploads")
public String upload(MultipartFile[] imgs, HttpServletRequest request) {
    List<String> file = new ArrayList<>();
    for(MultipartFile img : imgs){
        if (img.getSize() > 0) {
            //获取保存上传文件的file路径
            String path = request.getServletContext().getRealPath("file"); 
            //获取上传的文件名
            String name = img.getOriginalFilename();
            File file = new File(path,name);
            try {
                img.transferTo(file);
                //保存上传之后的文件路径
                files.add("/file/"+name);
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
	request.setAttribute("files",files);
    return "uploads";
}

下载

  • JSP
<body>
    <a herf = "/file/download/1">1.png</a>
    <a herf = "/file/download/2">2.png</a>
    <a herf = "/file/download/3">3.png</a>
</body>
  • Handler
@GetMapping("download/{name}")
public void download(String name,HttpServletRequest req,HttpServletResponse resp) {
    if(name != null){
        String path = request.getservletContext().getRealPath("file");
        File file = new File(path,name);
        Outputstream outputStream = null;
        if(file.exists()){
            resp.setContentType ("application/forc-download") ;
            resp.setHeader ("Content-Disposition","attachment;filename="+name);
            try {
                outputStream = response.getOutputstream();
                outputstream.write(FileUtils.readFileToByteArray(file));
                outputstream.flush();
            }catch (IOException e){
                e.printStackTrace();
            }finally {
                if (outputstream != null){
                    try {
                        outputstream.close();
                    }catch(IOException e) {
                        e.printStackTrace();
                    }
                }
            }
        }
    }
}

SpringMVC表单标签库

  • Handler
@GetMapping("/get")
public ModelAndview get(){
    ModelAndView modelAndView = new ModelAndView ("tag");
    Student student = new Student(1L,"张三",22);
    modelAndView.add0bject("student",student);
    return modelAndView;
}
  • JSP
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@ page isELIgnored="false" %>
<%@ taglib prefix="from" uri="http://www.springframework.org/tags/form" %>
<html>
<head>
    <title>Title</title>
</head>
<body>
    <form:form modelAttribute="student">
     	学生ID:<form:input path="id" /><br/>
		学生姓名:<form:input path="name" /><br/>
		学生年龄:<form:input path="age" /><br/>
        <input type="submit" value = "提交"/>
    </form:form>
</body>
</html>

  1. JSP页面导入Spring MVC表单标签库,与导入JSTL标签库的语法非常相似,前缀prefix可以自定义,通常定义为from。

    <%@ taglib prefix="from" uri="http://www.springframework.org/tags/form" %>
    
  2. 将form表单与模型数据进行绑定,通过modelAttribute属性完成绑定,将modelAttribute 的值设置为模型数据对应的key值。

    Handler:modelAndview.addObject("student",student);
    JSP:<form:form modelAttribute="student">
    
  3. form表单完成绑定之后,将模型数据的值取出绑定到不同的标签中,通过设置标签的path 属性完成,将path属性的值设置为模型数据对应的属性名即可。

    学生ID:<form:input path="id" /><br/>
    学生姓名:<form:input path="name" /><br/>
    学生年龄:<form:input path="age" /><br/>
    
常用表单标签
  • from
<form:from modelAttribute="student"/>

渲染的是 HTML中的<form/>,通过 modelAttribute属性绑定具体的模型数据。

  • input
<form:input path="name"/>

渲染的是HTML中的<input type="text"/>,from标签绑定的是模型数据,input标签绑定的是模型数据中的属性值,通过path属性可以与模型数据中的属性名对应,并且支持及联操作。

<from:input path="address.name"/>
  • password
<form:password path="password"/>

渲染的是HTML中的<input type="password"/>,通过path属性与模型数据的属性值进行绑定password标签的值不会在页面显示。

  • checkbox
<form:checkbox path="hobby" value="读书"/>

渲染的是HTML中的<input type="checkbox"/>,通过path与模型数据的属性值进行绑定,可以绑定boolean、数组和集合。

如果绑定boolean值,若该变量的值为true,则表示该复选框选中,否则表示不选中。

如果绑定数组或者集合,数组/集合中的元素等于checkbox的value值,则选中。

student.setHobby(Arrays.asList("读书","看电影","玩游戏"));
modelAndView.addObject("student",student) ;
爱好:
<form:checkbox path="hobby" value="摄影">
</form:checkbox>摄影<br/>
<form:checkbox path="hobby" value="读书">
</form:checkbox>读书<br/>
<form:checkbox path="hobby" value="听亲乐">
</form:checkbox>听音乐<br/>
<form:checkbox path="hobby" value="看电影">
</form:checkbox>看电影<br/>
<form:checkbox path="hobby" value="旅游">
</form:checkbox>旅游<br/>
<form:checkbox path="hobby" value="玩游戏">
</form:checkbox>玩游戏<br/>
<input type="submit" value="提交" />
  • checkboxes
<form:checkboxes items=${student.hobby} path="selectHobby" />

渲染的是HTML中的一组<input type="checkbox"/>,是对<form:checkbox/>的一种简化,需要结合items和path属性来使用,items绑定被遍历的集合或数组,path绑定被选中的集合或数组,可以这样理解,items为全部可选集合,path为默认的选中集合。

student.setHobby(Arrays.asList("摄影","读书","听音乐","看电影","旅游","玩游戏"));
student.setselectHobby(Arrays.asList("摄影","读书","听音乐"));
modelAndview.add0bject("student",student);
爱好:<form:checkboxes path="selectHobby" items="${student.hobby}"/><br/>

需要注意的是path可以直接绑定模型数据的属性值,items则需要通过EL表达式的形式从域对象中获取数据,不能直接写属性名。

  • rabiobutton
<from:radiobutton path="radioId" value=0/>

渲染的是HTML 中的一个<input type="radio"/>,绑定的数据与标签的value值相等则为选中,否则不选中。

student.setRadioId(1);
modelAndview.addObject("student" ,student) ;
radiobutton:<form:radiobutton path="radioId" value="1" />radiobutton<br/>
  • radiobuttons
<form:radiobuttons itmes="${student.grade}" path="selectGrade"/>

渲染的是HTML 中的一组<input type="radio"/>,这里需要结合items和 path 两个属性来使用items绑定被遍历的集合或数组,path绑定被选中的值,items为全部的可选类型,path 为默认选中的选项,用法与<form:checkboxes/>一致

Map<Integer,String> gradeMap = new HashMap<>();
gradeMap.put(1,"一年级");
gradeMap.put(2,"二年级");
gradeMap.put(3,"三年级");
gradeMap.put(4,"四年级");
gradeMap.put(5,"五年级");
gradeMap.put(6,"六年级");
student.setGradeMap(gradeMap);
student.setselectGrade(3);
modelAndview.add0bject("student",student);
学生年级:<form:radiobuttons items="${student.gradeMap}" path="selectGrade"/><br/>
  • select
<form:select items="${student.citys}" path="selectCity" />

渲染的是HTML中的一个<select/>标签,需要结合items和 path两个属性来使用,items绑定被遍历的集合或数组,path绑定被选中的值,用法与<from:radiobuttons/>一致。

Map<Integer,String> cityMap = new HashMap<>();
cityMap.put(1,"北京");
cityMap.put(2,"上海");
cityMap.put(3,"广州");
cityMap.put(4,"深圳");
student.setCityMap(cityMap);
student.setselectCity(3);
modelAndView.addobject ("student",student);
所在城市:<form:select items="${student.cityMap}" path="selectCity"></form:select><br/>
  • options

form:select结合form:options 的使用, from:select 只定义path属性,在form:select 标签内部添加一个子标签form:options,设置items属性,获取被遍历的集合。

所在城市:
<form:select path="selectCity">
<form:options items="${student.cityMap}"></form:options>
</form:select><br/>
  • option

form:select结合form:option的使用,from:select定义 path属性,给每一个form:option设置value值,path 的值与哪个value值相等,该项默认选中。

所在城市:
<form:select path="selectCity">
    <form:option value="1">杭州</form:option>
    <form:option value="2">成都</form:option>
    <form:option value="3">西安</form:option>
</form:select><br/>
  • textarea

渲染的是HTML 中的一个<textarea/> , path绑定模型数据的属性值,作为文本输入域的默认值。

student.setIntroduce("你好,我是...");
modelAndview.addObject("student "student);
信息:<form:textarea path="introduce"/><br/>
  • errors

处理错误信息,一般用在数据校验,该标签需要结合Spring MVC的验证器结合起来使用。

SpringMVC数据校验

Spring MVC提供了两种数据校验的方式:

  1. 基于Validator接口。
  2. 使用Annotation JSR - 303标准进行校验。
  • 基于Validator接口的方式需要自定义Validator验证器,每一条数据的验证规则需要开发者**手动完成**使用
  • Annotation JSR- 303标准则不需要自定义验证器,通过注解的方式可以直接在实体类中添加每个属性的验证规则,这种方式更加方便,实际开发中推荐使用。

基于Validator接口

  • 实体类Account
@Data
public class Account {
    private String name;
    private String password;
}

自定义验证器AccountValidator,实现Validator接口

public class AccountValidator implements Validator {
    @Override
    public boolean supports(Class<?> clazz) {
        return Account.class.equals(clazz);
    }

    @Override
    public void validate(Object target, Errors errors) {
        ValidationUtils.rejectIfEmpty(errors,"name",null,"姓名不能为空");
        ValidationUtils.rejectIfEmpty(errors,"password",null,"密码不能为空");
    }
}
  • 控制器
@RestController
@RequestMapping("/validator")
public class ValidatorHandler {


    @GetMapping("/login")
    public String login(Model model){
        model.addAttribute("account",new Account());
        return "login";
    }
    
      @PostMapping("/login")
    public String login(@Validated Account account , BindingResult bindingResult){
        if (bindingResult.hasErrors()) {
            return "login";
        }
        return "index";
    }
}
  • springmvc.xml配置验证器
<!--    基于 Validator的配置-->
<bean id="accountValidator" class="com.liner.springmvc.validator.AccountValidator"/>
<mvc:annotation-driven validator="accountValidator" />
  • JSP
<body>
    <form:form modelAttribute="account" action="/validator/login" method="post">
        姓名: <form:input path="name"/><br/><form:errors path="name"/>
        密码: <form:password path="password" /><br/><form:errors path="password"/>
        <input type="submit" value="登录">
    </form:form>
</body>

基于Annotation JSR- 303标准

使用Annotation JSR-303标准进行验证,需要导入支持这种标准的依赖jar文件,这里我们使用HibernateValidator。

  • pom.xml
<dependency>
    <groupId>org.hibernate.validator</groupId>
    <artifactId>hibernate-validator</artifactId>
    <version>6.2.3.Final</version>
</dependency>

<dependency>
    <groupId>javax.validation</groupId>
    <artifactId>validation-api</artifactId>
    <version>2.0.1.Final</version>
</dependency>

<dependency>
    <groupId>org.jboss.logging</groupId>
    <artifactId>jboss-logging</artifactId>
    <version>3.4.3.Final</version>
</dependency>

通过注解的方式直接在实体类中添加相关的验证规则。

@Data
public class Person {
    @NotEmpty (message = "用户名不能为空");
    private String username;
    @size(min = 6 ,max = 12,message ="密码6-12位")
    private Sring password;
    @Email(regexp = "^[a-zA-Z0-9_.-]+@[a-zA-Z0-9-]+(\\\\.[a-zA-Z0-9-]+)*\\\\.[a-zA-Z0-9]{2,6}$",message ="请输入正确的邮箱格式")
    private String email;
    @Pattern(regexp = "^((13[0-9])|(14[5|7])|(15([0-3]|[5-9]))|(18[0,5-9]))\\\\\\\\d{8}$",message ="请输入正确的电话")
    private String phone;
}
  • ValidatorHandler
@GetMapping("/register")
public String register(Model model){
    model.addAttribute("person",new Person());
    return "register";
}

@PostMapping("/register")
public String register(@Valid Person person , BindingResult bindingResult){
    if (bindingResult.hasErrors()) {
        return "register";
    }
    return "index";
}
  • springmvc.xml
<mvc:annotation-driven />
  • JSP
<body>
    <form:form modelAttribute="person" action="/validator/register" method="post">
        姓名: <form:input path="name"/><form:errors path="name"/><br/>
        密码: <form:password path="password" /><form:errors path="password"/><br/>
        邮箱: <form:input path="email"/><form:errors path="email"/><br/>
        电话: <form:password path="pone" /><form:errors path="pone"/><br/>
        <input type="submit" value="提交">
    </form:form>
</body>
校验规则详解
规则解释
@Null被注解的元素必须为null
@NotNull被注解的元素不能为null
@Min(value)被注解的元素必须是一个数字,其值必须大于等于指定的最小值
@Max(value)被注解的元素必须是一个数字,其值必须小于于等于指定的最大值
@Email被注解的元素必须是电子邮箱地址
@Pattern被注解的元素必须符合对应的正则表达式
@Length被注解的元素的大小必须在指定的范围内
@NotEmpty被注解的字符串的值必须非空

NullEmpty 是不同的结果,String str = null,str是null,String str =“”, str 不是null,其值为空。

MyBatis

MyBatis概述

MvBatis 是apache的一个开源项目iBatis,2010年这个项目由apache softwarefoundation迁移到了google code,并且改名为MvBatis,2013年11月迁移到Github.。
MBatis,是一个==实现了数据持久化的开源框架,简单理解就是对JDBC进行封装==

数据持久化:ORMapping: Object Relationship Mapping 对象关系映射

  • 对象:指面向对象
  • 关系:指关系型数据库
  • 映射:Java到 MySQL的映射,开发者可以以面向对象的思想来管理数据库。

MyBatis的优点

  • 与JDBC相比,减少了50%以上的代码量。

  • MvBatis是最简单的持久化框架,小巧并且简单易学。

  • MvBatis相当灵活,不会对应用程序或者数据库的现有设计强加任何影响,SQL写在XML里,从程序代码中彻底分离,降低耦合度,

    便于统一管理和优化,并可重用。

  • 提供XML标签,支持编写动态sQL语句。

  • 提供映射标签,支持对象与数据库的ORM字段关系映射。

MyBatis的缺点

  • SQL语句的编写工作量较大,尤其是字段多、关联表多时,更是如此,对开发人员编写SQL语句的功底有一定要求。
  • SQL语句依赖于数据库,导致数据库移植性差,不能随意更换数据库。

MyBatis核心接口和类

build方法
openSession方法
SqlSessionEactoryBuilder
SqlSessionFactory
SqlSession

如何使用MyBatis

  • 新建Maven工程,pom.xml
<dependency>
    <groupId>org.mybatis</groupId>
    <artifactId>mybatis</artifactId>
    <version>3.5.9</version>
</dependency>

<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <version>8.0.28</version>
</dependency>

  • 新建数据库表,t_account
use mybatis;
create table t_account (
    id int primary key auto_increment,
    username varchar(11),
    password varchar(11),
    age int
}

  • 新建数据库表对应的实体类,Account
@Data
public class Account {
    private long id;
    private String username;
    private String password;
    private int age;
}

创建MyBatis的配置文件 config.xml,文件名可自定义

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
        PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
    <!--配置MyBatis运行环境-->
    <environments default="development">
        <environment id="development">
            <!--配置JDBC事务管理-->
            <transactionManager type="JDBC"></transactionManager>
            <!-- POOLED配置JDBC数据源连接池-->
            <dataSource type="POOLED">
                <property name="driver" value="com.mysgl.cj.jdbc.Driver"></property>
                <property name="url" value="jdbc:mysql: //localhost:3306/mybatis?
                    useUnicode=true&amp; characterEncoding=UTF-8"></property>
                <property name="username" value="root"></property>
                <property name="password" value="123456"></property>
                </dataSource>
            </environment>
    </environments>
</configuration>

使用原生接口

1、MyBatis框架需要开发者自定义SQL语句,写在Mapper.xml文件中,实际开发中,会为每个实体类创建对应的Mapper.xml ,定义管理该对象数据的SQL。

<mapper namespace="com.liner.mapper.AccoutMapper">
    <insert id="save" parameterType=" com.liner.entity.Account">
        insert into t_account(username,password,age) values(#{username},#{password},#{age})
    </insert>
</mapper>
  • namespace通常设置为文件所在包+文件名的形式。

  • insert标签表示执行添加操作。

  • select标签表示执行查询操作。

  • update标签表示执行更新操作。

  • delete标签表示执行删除操作。

  • id是实际调用 MyBatis方法时需要用到的参数。

  • parameterType是调用对应方法时参数的数据类型。

2、在全局配置文件config.xml中注册AccountMapper.xml

<!--注册AccountMapper.xml -->
<mappers>
    <mapper resource="com/liner/mapper/AccountMapper.xml"></mapper>
</mappers>

3、调用MyBaits的原生接口执行添加操作

public class Test {
    public static void main (String[] args) {
        //加载MyBatis配置文件
        InputStream inputStream = Test.class.getClassLoader().getResourceAsStream("config.xml");
        SqlSessionFactoryBuilder sqlSessionFactoryBuilder = new SqlSessionFactoryBuilder();
        SqlSessionFactory sqlSessionFactory = sqlSessionFactoryBuilder.build(inputStream);
        SqlSession sqlSession = sqlSessionFactory.openSession();
        String statement = "com.liner.mapper.AccoutMapper.save" ;
        Account account = new Account(1L,"张三","123123",22);
        sqlSession.insert(statement, account);
        sqlSession.commit();
    }
}

使用Mapper代理实现自定义接口

  • 自定义接口,定义相关业务方法。
  • 编写与方法相对应的Mapper.xml。

1、自定义接口

public interface AccountRepository {
    public int save(Account account);
    public int update(Account account);
    public int deleteById(long id);
    public List<Account> findAll();
    public Account findById(long id);

2、创建接口对应的Mapper.xml,定义接口方法对应的SQL语句。

statement标签可根据SQL执行的业务选择insert、delete、update、select。

MyBatis框架会根据规则自动创建接实现类的代理对象。

规则:

  • Mapper.xml 中 namespace 为接口的全类名。
  • Mapper.xml 中 statementid 为接口中对应的方法名。
  • Mapper.xml 中 statement 的 parameterType 和接口中对应方法的参数类型一致。
  • Mapper.xml 中 statement 的 resultType 和接口中对应方法的返回值类型一致。
<mapper namespace="com.liner.repository.AccountRepository">
    <insert id="save" parameterType="com.liner.entity.Account">
        insert into t_account(username,password,age) values(#{username},#{password},#{age})
    </insert>
    <update id="update" parameterType="com.liner.entity.Account">
        update t_account set username = #{username},password = #{password},age =#{age} where id =#{id}
    </update>
    <delete id="deleteById" parameterType="long">
        delete from t_account where id= #{id}
    </delete>
    <select id="findAll" resultType="com.liner.entity.Account">
        select * from t_account
    </select>
    <select id="findById" parameterType="long" resultType="com.liner.entity.Account">
        select * from t_account where id =#{id}
    </select>
</mapper>

3、在config.xml中注册AccountRepository.xml

<!--注册AccountMapper.xml -->
<mappers>
    <mapper resource=" com/liner/mapper/AccountMapper.xml"></mapper>
    <mapper resource=" com/liner/repository/AccountRepository.xml"></mapper>
</mappers>

4、调用接口的代理对象完成相关的业务操作

public class Test {
    public static void main (String[] args) {
        //加载MyBatis配置文件
        InputStream inputStream = Test.class.getClassLoader().getResourceAsStream("config.xml");
        SqlSessionFactoryBuilder sqlSessionFactoryBuilder = new SqlSessionFactoryBuilder();
        SqlSessionFactory sqlSessionFactory = sqlSessionFactoryBuilder.build(inputStream);
        SqlSession sqlSession = sqlSessionFactory.openSession();
        //获取实现接口的代理对象
        AccountRepository accountRepository = sqlSession.getMapper(AccountRepository.class);

        //添加对象
        /*
        Account account = new Account(1L,"张三","123123",22);
        int result = accountRepository.save(account);
        sqlSession.commit();
        sqlSession.close();
        */

        //查询全部对象
        /*
        List<Account> list = accountRepository.findAll();
        for(Account account : list){
        	System.out.println(account);
        }
        sqlSession.close();
        */

        //通过id查询对象
        /*
        Account account = accountRepository.findById(1L);
        System.out.println(account);
        sqlSession.close();
        */

        //修改对象
        /*
        Account account = accountRepository.findById(1L);
        account.setUsername(“小王”);
        account.setPassword(“123123”);
        account.setAge(21);
        int result = accountRepository.update(account);
        sqlSession.commit();
        System.out.println(result);
        sqlSession.close();
        */

        //通过id删除对象
        int result = accountRepository.deleteById(1L);
        System.out.println(result);
        sqlSession.commit();
        sqlSession.close();
    }
}

Mapper.xml

  • statement标签:select、update、delete、insert 分别对应查询、修改、删除、添加操作。

  • parameterType:参数数据类型

    1. 基本数据类型,通过id查询Account

      <select id="findById" parameterType="long" resultType=" com.liner.entity.Account">
          select * from t_account where id = #{id}
      </select>
      
    2. String类型,通过name查询Account

      <select id="findByName" parameterType="java.lang.String"resultType=" com.liner.entity.Account">
          select * from t_account where username = #{username}
      </select>
      
    3. 包装类,通过id查询Account

      <select id="findById" parameterType="java.lang.Long"resultType="com.liner.entity.Account">
          select * from t_account where id = #{id}
      </select>
      
    4. 多个参数,通过name和age查询Account

      <select id="findByNameAndAge" resultType="com.liner.entity.Account">
          select * from t_account where username = #{arg0} and age = #{arg1}
      </select>
      
    5. Java Bean

      <update id="update" parameterType="com.liner.entity.Account">
          update t_account set username = #(username},password = #{password},age = #{age} whereid =#{id}
      </update>
      
  • resultType:结果类型

    1. 基本数据类型,统计Account总数

      <select id="count" resultType="int">
          select count (id) from t_account
      </select>
      
    2. 包装类,统计Account总数

      <select id="count" resultType="java.lang.Integer">
          select count(id) from t_account
      </select>
      
    3. tring 类型,通过id查询Account 的 name

      <select id="findNameById" resultType="java.lang.String">
          select username from t_account where id =#{id}
      </select>
      
    4. Java Bean

      <select id="findById" parameterType="long" resultType= " com.liner.entity.Account">
          select * from t_account where id = #{id}
      </select>
      

级联查询

  • 一对多

Student

@Data
public class Student {
    private long id;
    private String username;
    private String classes;
}

Classes

@Data
public class Classes {
    private long id;
    private String username;
    private List<Student> students;
}

StudentRepository

public interface StudentRepository {
    public Student findById(long id);
}

StudentRepository.xml

<mapper namespace="com.liner.repository.StudentRepository">
    <resultMap id="studentMap" type="com.liner.entity.Student">
        <id column="id" property="id"></id>
        <result column="name" property="name "></result>
        <association property="classes" javaType="com.liner.entity.classes">
            <id column="cid" property="id"></id>
            <result column="cname" property="name "></result>
        </association>
    </resultMap>
    
    <select id="findById" parameterType="long" resultMap="studentMap">
        select s.id,s.name,c.id as cid,c.name as cname from student s,classes c where s.id =
        #{id} and s.cid = c.id
    </select>
</mapper>
  • 多对一

ClassesRepository

public interface ClassesRepository {
    public Classes findById(long id);
}

ClassesRepository.xml

<mapper namespace="com.liner.repository.ClassesRepository">
    <resultMap id="classesMap" type="com.liner.entity.Classes">
        <id column="cid" property="id"></id>
        <result column="cname" property="name "></result>
        <collection property="students" ofType="com.liner.entity.Student">
            <id column="id" property="id"/>
            <result column="name" property="name"/>
        </collection>
    </resultMap>

    <select id="findById" parameterType="long" resultMap="classesMap">
        select s.id,s.name,c.id as cid,c.name as cname from student s,classes c where c.id =
        #{id} and s.cid = c.id
    </select>
</mapper>
  • 多对多

Customer

@Data
public class Customer {
    private long id;
    private String name;
    private List<Goods> goods;
}

Goods

@Data
public class Goods {
    private long id;
    private String name;
    private List<Customer> customers;
}

CustomerRepository

public interface CustomerRepository {
    public Customer findById(long id);
}

CustomerRepository.xml

<mapper namespace="com.liner.repository.CustomerRepository">
    <resultMap id="customerMap" type=" com.liner.entity.Customer">
        <id column="cid" property="id"></id>
        <result column="cname" property="name"></result>
        <collection property="goods" ofType=" com.liner.entity.Goods">
            <id column="gid" property="id" />
            <result column="gname" property= " name" /></collection>
    </resultMap>
    <select id="findById" parameterType="long" resultMap="customerMap">
        select c.id cid,c.name cname,g.id gid,g.name gname from customer c, goods
        g, customer_goods cg where c.id = #{id} and cg.cid = c.id and cg.gid = g.id
    </select>
</mapper>

GoodsRepository

public interface GoodsRepository {
    public Goods findById(long id);
}

GoodsRepository.xml

<mapper namespace="com.liner.repository.GoodsRepository">
    <resultMap id="goodsMap" type="com.liner.entity.Goods">
        <id column="gid" property="id"></id>
        <result column="gname" property="name"></result>
        <collection property=" customers" ofType="com.liner.entity.Customer">
            <id column="cid" property="id" />
            <result column= "cname" property="name"/></ collection>
    </resultMap>
    <select id="findById" parameterType="long" resultMap="goodsMap">
        select c.id cid,c.name cname,g.id gid, g.name gname from customer c, goods
        g,customer_goods cg where g.id = #{id} and cg.cid = c.id and cg.gid = g.id
    </select>
</mapper>

逆向工程

MyBatis框架需要:实体类、自定义Mapper接口、Mapper.xml
传统的开发中上述的三个组件需要开发者手动创建,逆向工程可以帮助开发者自动创建三个组件,减轻开发者的工作量,提高工作效率。

如何使用

MyBatis Generator ,简称MBG,是一个专门为MyBatis框架开发者定制的代码生成器,可自动生成MyBatis框架所需的实体类、Mapper接口、Mapper.xml,支持基本的CRUD操作,但是一些相对复杂的SQL需要开发者自己来完成。

  • 新建Maven工程,pom.xml
<dependency>
    <groupId>org.mybatis</groupId>
    <artifactId>mybatis</artifactId>
    <version>3.5.9</version>
</dependency>

<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <version>8.0.28</version>
</dependency>
<dependency>
    <groupId>org.mybatis.generator</groupId>
    <artifactId>mybatis-generator-core</artifactId>
    <version>1.3.5</version>
</dependency>
  • 创建MBG配置文件 generatorConfig.xml
  1. jdbcConnection配置数据库连接信息。
  2. javaModelGenerator配置JavaBean 的生成策略。
  3. sqlMapGenerator配置SQL映射文件生成策略。
  4. javaClientGenerator配置Mapper接口的生成策略。
  5. table配置目标数据表(tableName:表名,domainObjectName: JavaBean类名)。
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE generatorConfiguration
        PUBLIC "-//mybatis.org//DTD MyBatis Generator Configuration 1.0//EN"
        "http://mybatis.org/dtd/mybatis-generator-config_1_0.dtd">
<generatorConfiguration>
    <context id="testTables" targetRuntime="MyBatis3">
        <jdbcConnection
                        driverclass=" com.mysql.cj.jdbc.Driver"
                        connectionURL="jdbc :mysql://localhost:3306/mybatis?useunicode=true&amp; characterEncoding=UTF-8"
                        userId="root"
                        password="123456"
                        />
        <javaModelGenerator targetPackage="com.liner.entity"
                            targetProject="./src/main/java"/>
        <sqlMapGenerator targetPackage="com.liner.repository"
                         targetProject="./src/main/java"/>
        <javaclientGenerator type="XMLMAPPER" targetPackage="com.liner.repository" targetProject="./src/main/java"/>
        <table tableName="t_user" domainobjectName="User"/>
    </context>
</generatorConfiguration>
  • 创建Generator执行类
public class Main {
    public static void main(String[] args) {
        List<String> warings = new ArrayList<>();
        boolean overwrite = true;
        String genCig = "/generatorConfig.xml";
        File configFile = new File(Main.class.getResource(genCig).getFile());
        ConfigurationParser configurationParser = new ConfigurationParser(warings);
        Configuration configuration = null;
        try {
            configuration = configurationParser.parseConfiguration(configFile);
        }catch (IOException e) {
            e.printStackTrace( );
        } catch (XMLParseException e){
            e.printStackTrace( );
        }
        DefaultShellCallback callback = new DefaultShellCallback(overwrite);
        MyBatisGenerator myBatisGenerator = null;
        try {
            myBatisGenerator = new MyBatisGenerator(configuration,callback ,warings);
        }catch (InvalidConfigurationException e) {
            e.printStackTrace( );
        }
        try {
            myBatisGenerator.generate(null);
        }catch (SQLException e) {
            e.printStackTrace( );
        }catch (IOException e) {
            e.printStackTrace( );
        }catch (InterruptedException e){
            e.printStackTrace( );
        }
    }
}

延迟加载

延迟加载也叫懒加载、惰性加载,使用延迟加载可以提高程序的运行效率,针对于数据持久层的操作,在某些特定的情况下去访问特定的数据库,在其他情况下可以不访问某些表,从一定程度上减少了Java应用与数据库的交互次数。

查询学生和班级信息时,学生和班级是两张不同的表,如果当前需求只需要获取学生的信息,那么查询学生单表即可,如果需要通过学生获取对应的班级信息,则必须查询两张表。不同的业务需求,需要查询不同的表,根据具体的业务需求来动态减少数据表查询的工作就是延迟加载。

  • 在config.xml中开启延迟加载
<settings>
    <!--打印SQL-->
    <setting name="logImpl" value="STDOUT_LOGGING"/>
    <!--开启延迟加载-->
    <setting name="lazyLoadingEnabled" value="true"/>
</settings>
  • 将多表关联查询拆分成多个单表查询

StudentRepository

public Student findByIdLazy(long id);

StudentRepository.xml

<resultMap id="studentMapLazy" type="com.liner.entity.Student">
    <id column="id" property="id"/>
    <result column="name" property="name"/>
   	<association property="classes" javaType=" com.liner.entity.classes"
select= "com.liner.repository.ClassesRepository.findByIdLazy" column="cid"/>
</resultMap>
<select id="findByIdLazy" parameterType="long" resultMap="studentMapLazy">
    select * from student where id = #{id}
</select>

ClassesRepository

public Classes findByIdLazy(long id);

ClassesRepository.xml

<select id="findByIdLazy" parameterType="long" resultType="com.liner.entity.Classes">
    select * from classes where id = #{id}
</select>

MyBatis缓存

  • 什么是MyBatis缓存

使用缓存可以减少Java应用与数据库的交互次数,从而提升程序的运行效率。比如查询出id =1的对象,第一次查询出之后会自动将该对象保存到缓存中,当下一次查询时,直接从缓存中取出对象即可,无需再次访问数据库。

  • MyBatis缓存分类
  1. 一级缓存:SqlSession级别,默认开启,并且不能关闭。
    操作数据库时需要创建 SqlSession对象,在对象中有一个HashMap用于存储缓存数据,不同的SqlSession之间缓存数据区域是互不影响的。
    一级缓存的作用域是SqlSession范围的,当在同一个SqlSession 中执行两次相同的SQL语句时,第一次执行完毕会将结果保存到缓存中,第二次查询时直接从缓存中获取。
    需要注意的是,如果SqlSession执行了DML操作(insert、update、delete) ,MyBatis 必须将缓存清空以保证数据的准确性
  2. 二级缓存: Mapper级别,默认关闭,可以开启。
    使用二级缓存时,多个SqlSession使用同一个Mapper的SQL语句操作数据库,得到的数据会存在二级缓存区,同样是使用HashMap进行数据存储,相较于一级缓存,二级缓存范围更大,多个SqlSession可以共用二级缓存,二级缓存是跨SqlSession的。
    二级缓存是多个SqlSession共享的,其作用域是Mapper的同一个namespace,不同的 SqlSession 两次执行相同的namespace下的SQL语句,参数也相等,则第一次执行成功之后会将数据保存到二级缓存中,第二次可直接从二级缓存中取出数据。

代码

  • 一级缓存
public class Test {
    public static void main(String[] args) {
        InputStream inputStream = Test.class.getClassLoader().getResourceAsStream("config.xml");
        SqlSessionFactoryBuilder sqlSessionFactoryBuilder = new SqlSessionFactoryBuilder();
        SqlSessionFactory sqlSessionFactory = sqlSessionFactoryBuilder.build(inputStream);
        SqlSession sqlSession = sqlSessionFactory.openSession();
        AccountRepository accountRepository = sqlSession.getMapper(AccountRepository.class);
        Account account = accountRepository.findById(1L);
        System.out.println(account);//查询1次  ———— 此时无缓存,此次查询过后会存到HashMap中
        //Account account1 = accountRepository.findById(1L);
        //System.out.println(account1);//此时的查询结果:查询1次,但会得到两次对象

        sqlSession.close();//关闭第一个SqlSession,
        //Account account1 = accountRepository.findById(1L);
        //System.out.println(account1);//此时的查询结果:抛出异常  Executor 已经关闭

        sqlSessionFactory.openSession();//新建了第二个SqlSession,重新实例化
        //Account account1 = accountRepository.findById(1L);
        //System.out.println(account1);//此时的查询结果:仍然抛出异常 Executor 已经关闭

        accountRepository = sqlSession.getMapper(AccountRepository.class);
        Account account1 = accountRepository.findById(1L);
        System.out.println(account1);//查询2次,因为此时 已经换成第二个SqlSession,而第二个SqlSession中无缓存
    }
}
  • 二级缓存

  • MyBatis自带的二级缓存

    • config.xml 只配置开启二级缓存
    <settings>
        <!--打印sQL-->
        <setting name="logImpl" value=" STDOUT_LOGGING"/>
        <!--开启延迟加载-->
        <setting name="lazyLoadingEnabled" value="true"/>
        <!--开启二级缓存-->
        <setting name="cacheEnabled" value="true"/>
    </settings>
    
    • Mapper.xml中配置缓存
    <cache></cache>
    
    • 实体类实现序列化接口
    @Data
    @AllArgsConstructor
    @NoArgsConstructor
    public class Account implements Serializable {
        private long id;
        private String username;
        private String password;
        private int age;
    }
    
  • ehcache第三方的二级缓存

    • pom.xml,添加相关依赖
    <dependency>
        <groupId>org.mybatis</groupId>
        <artifactId>mybatis-ehcache</artifactId>
        <version>1.0.0</version>
    </dependency>
    <dependency>
        <groupId>net.sf.ehcache</groupId>
        <artifactId>ehcache-core</artifactId>
        <version>2.6.6</version>
    </dependency>
    
    • 添加ehcache.xml
    <ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
             xsi:noNamespaceschemaLocation="../config/ehcache.xsd">
        <diskStore/>
        <defaultCache
                      maxElementsInMemory="1000"
                      maxElementsonDisk="10000000"
                      eternal="false"
                      overflowToDisk="false"
                      timeToIdleseconds="120"
                      timeToLiveseconds="120"
                      diskExpiryThreadIntervalseconds="120"
                      memoryStoreEvictionPolicy="LRU">
        </defaultCache>
    </ehcache>
    
    • config.xml配置开启二级缓存
    <settings>
        <!--打印sQL-->
        <setting name="logImpl" value=" STDOUT_LOGGING"/>
        <!--开启延迟加载-->
        <setting name="lazyLoadingEnabled" value="true"/>
        <!--开启二级缓存-->
        <setting name="cacheEnabled" value="true"/>
    </settings>
    
    • Mapper.xml中配置缓存
    <cache type="org.mybatis.caches.ehcache.EhcacheCache">
        <!--缓存创建之后,最后一次访问缓存的时间至缓存失效的时间间隔-->
        <property name="timeToIdleseconds" value="3600"/>
        <!--缓存自创建时间起至失效的时间间隔-->
        <property name="timeToLiveseconds" value="3600"/>
        <!--缓存回收策略,LRU表示移除近期使用最少的对象-->
        <property name="memoryStoreEvictionPolicy" value="LRU"/>
    </cache>
    
    • 实体类不需要实现序列化接口
    @Data
    @AllArgsConstructor
    @NoArgsConstructor
    public class Account{
        private long id;
        private String username;
        private String password;
        private int age;
    }
    

动态SQL

使用动态SQL可**简化代码的开发**,减少开发者的工作量,程序可以自动根据业务参数来决定SQL的组成。

  • if标签

if标签可以自动根据表达式的结果来决定是否将对应的语句添加到SQL中,如果条件不成立则不添加,如果条件成立则添加。

<select id="findByAccount" parameterType="com.liner.entity.Account" resultType="com.liner.entity.Account">
    select * from t_account where
    <if test="id!=0">
        id = #{id}
    </if>
    <if test="username!=null">
        and username = #{username}
    </if>
    <if test="password!=null">
        and password = #{password}
    </if>
    <if test="age!=0">
        and age = #{age}
    </if>
</select>
  • where标签

where标签可以自动判断是否要删除语句块中的and关键字,如果检测到where直接跟and拼接,则自动删除and,通常情况下if和where 结合起来使用。

<select id="findByAccount" parameterType="com.liner.entity.Account" resultType="com.liner.entity.Account">
    select * from t_account
    <where>
        <if test="id!=0">
            id = #{id}
        </if>
        <if test="username!=null">
            and username = #{username}
        </if>
        <if test="password!=null">
            and password = #{password}
        </if>
        <if test="age!=0">
            and age = #{age}
        </if>
    </where>
</select>
  • choose , when标签
<select id="findByAccount" parameterType="com.liner.entity.Account" resultType="com.liner.entity.Account">
    select * from t_account
    <where>
        <choose>
            <when test="id!=0">
                id = #{id}
            </when>
            <when test="username!=null">
                username = #{username}
            </when>
            <when test="password!=null">
                password = #{password}
            </when>
            <when test="age!=0">
                age = #{age}
            </when>
        </choose>
    </where>
</select>
  • trim标签

trim标签中的prefix和suffix属性会被用于生成实际的SQL语句,会和标签内部的语句进行拼接,如果语句前后出现了prefixOverrides或者suffixOverrides属性中指定的值,MyBatis框架会自动将其删除。

<select id="findByAccount" parameterType="com.liner.entity.Account" resultType="com.liner.entity.Account">
    select * from t_account
    <trim prefix="where" prefixoverrides="and">
        <if test="id!=0">
            id = #{id}
        </if>
        <if test="username!=null">
            and username = #{username}
        </if>
        <if test="password!=null">
            and password = #{password}
        </if>
        <if test="age!=0">
            and age = #{age}
        </if>
    </trim>
</select>
  • set标签

set标签用于update操作,会自动根据参数选择生成SQL语句。

<update id="update" parameterType="com.liner.entity.Account">
    update t_account
    <set>
        <if test="username!=null">
            username = #{username},
        </if>
        <if test="password!=null">
            password = #{password},
        </if>
        <if test="age!=0">
            age = #{age}
        </if>
    </set>
    where id =#{id}
</update>
  • foreach标签

foreach标签可以迭代生成一系列值,这个标签主要用于SQL的in语句。

<select id="findByAccount" parameterType="com.liner.entity.Account" resultType="com.liner.entity.Account">
    select * from t_account
    <where>
        <foreach collection="ids" open="id in (" close=")" item="id"
                 separator=",">
            #{id}
        </foreach>
    </where>
</select>

SSM框架整合

Spring + Spring MVC + MyBatis

  • Spring MVC负责实现MVC设计模式,

  • MyBatis 负责数据持久层,

  • Spring 负责管理Spring MVC和MyBatis相关对象的创建和依赖注入。

  1. 创建Maven工程,pom.xml

    dependencies>
    
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-webmvc</artifactId>
            <version>5.3.19</version>
        </dependency>
    
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-jdbc</artifactId>
            <version>5.3.19</version>
        </dependency>
    
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-aop</artifactId>
            <version>5.3.19</version>
        </dependency>
    
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-aspects</artifactId>
            <version>5.3.19</version>
        </dependency>
    
        <dependency>
            <groupId>org.mybatis</groupId>
            <artifactId>mybatis</artifactId>
            <version>3.5.9</version>
        </dependency>
    
        <dependency>
            <groupId>org.mybatis</groupId>
            <artifactId>mybatis-spring</artifactId>
            <version>2.0.7</version>
        </dependency>
    
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>8.0.28</version>
        </dependency>
    
        <dependency>
            <groupId>c3p0</groupId>
            <artifactId>c3p0</artifactId>
            <version>0.9.1.2</version>
        </dependency>
    
        <dependency>
            <groupId>jstl</groupId>
            <artifactId>jstl</artifactId>
            <version>1.2</version>
        </dependency>
    
        <dependency>
            <groupId>javax.servlet</groupId>
            <artifactId>javax.servlet-api</artifactId>
            <version>4.0.1</version>
        </dependency>
    
    </dependencies>
    
  2. web.xml中配置SpringMVC、Spring、字符编码过滤器、加载静态资源

    <web-app>
      <display-name>Archetype Created Web Application</display-name>
      <!-- 启动Spring -->
      <context-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>classpath:spring.xml</param-value>
      </context-param>
      <listener>
        <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
      </listener>
    
      <!-- Spring MVC -->
      <servlet>
        <servlet-name>dispatcherServlet</servlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
        <init-param>
          <param-name>contextConfigLocation</param-name>
          <param-value>classpath:springmvc.xml</param-value>
        </init-param>
      </servlet>
      
      <servlet-mapping>
        <servlet-name>dispatcherServlet</servlet-name>
        <url-pattern>/</url-pattern>
      </servlet-mapping>
    
      <!-- 字符编码过滤器 -->
      <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>
      
      <!-- 加载静态资源 -->
      <servlet-mapping>
        <servlet-name>default</servlet-name>
        <url-pattern>*.js</url-pattern>
      </servlet-mapping>
      <servlet-mapping>
        <servlet-name>default</servlet-name>
        <url-pattern>*.css</url-pattern>
      </servlet-mapping>
      <servlet-mapping>
        <servlet-name>default</servlet-name>
        <url-pattern>*.jpg</url-pattern>
      </servlet-mapping>
    </web-app>
    
  3. 在 spring.xml 中配置 MyBatis 和 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"
           xmlns:context="http://www.springframework.org/schema/context"
           xmlns:p="http://www.springframework.org/schema/p"
           xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.2.xsd
    	http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.3.xsd
    ">
    
        <!-- 整合MyBatis -->
        <bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
            <property name="user" value="root"></property>
            <property name="password" value="123456"></property>
            <property name="jdbcUrl" value="jdbc:mysql://localhost:3306/test?useUnicode=true&amp;characterEncoding=UTF-8"></property>
            <property name="driverClass" value="com.mysql.cj.jdbc.Driver"></property>
            <property name="initialPoolSize" value="5"></property>
            <property name="maxPoolSize" value="10"></property>
        </bean>
    
        <!-- 配置MyBatis SqlSessionFactory -->
        <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
            <property name="dataSource" ref="dataSource"></property>
            <property name="mapperLocations" value="classpath:com/liner/repository/*.xml"></property>
            <property name="configLocation" value="classpath:config.xml"></property>
        </bean>
    
        <!-- 扫描自定义的Mapper接口 -->
        <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
            <property name="basePackage" value="com.liner.repository"></property>
        </bean>
    
    </beans>
    
  4. config.xml 配置一些 MyBatis 辅助信息,比如打印 SQL 等。

    <configuration>
        <settings>
            <!-- 打印SQL-->
            <setting name="logImpl" value="STDOUT_LOGGING" />
        </settings>
    
        <typeAliases>
            <!-- 指定一个包名,MyBatis会在包名下搜索需要的JavaBean-->
            <package name="com.liner.entity"/>
        </typeAliases>
    </configuration>
    
  5. 配置 springmvc.xml

    <?xml version="1.0" encoding="UTF-8"?>
    <beans xmlns="http://www.springframework.org/schema/beans"
           xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
           xmlns:context="http://www.springframework.org/schema/context"
           xmlns:mvc="http://www.springframework.org/schema/mvc"
           xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
            http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.0.xsd
            http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc-3.2.xsd">
    
        <!-- 启动注解驱动 -->
        <mvc:annotation-driven></mvc:annotation-driven>
    
        <!-- 扫描业务代码 -->
        <context:component-scan base-package="com.liner"></context:component-scan>
    
        <!-- 配置视图解析器 -->
        <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
            <property name="prefix" value="/"></property>
            <property name="suffix" value=".jsp"></property>
        </bean>
    
    </beans>
    
  6. 创建实体类,User

    @Data
    public class User {
        private long id;
        private String name;
        private String password;
        private double score;
    }
    
  7. UserRepository

    public interface UserRepository {
        public List<User> findAll();
    }
    
  8. UserRepository.xml

    <mapper namespace="com.liner.repository.UserRepository">
        <select id="findAll" resultType="User">
            select * from user
        </select>
    </mapper>
    
  9. UserService

    public interface UserService {
        public List<User> findAll();
    }
    
  10. UserServiceImpl

    @Service
    public class UserServiceImpl implements UserService {
    
        @Autowired
        private UserRepository userRepository;
    
        @Override
        public List<User> findAll() {
            return userRepository.findAll();
        }
    }
    
  11. UserHandler

    @Controller
    @RequestMapping("/user")
    public class UserHandler {
        @Autowired
        private UserService userService;
    
        @GetMapping("/findAll")
        public ModelAndView findAll(){
            ModelAndView modelAndView = new ModelAndView();
            modelAndView.setViewName("index");
            modelAndView.addObject("list",userService.findAll());
            return modelAndView;
        }
    }
    

MyBatisPlus

国产的开源框架,基于MyBatis进行的升级优化,核心功能就是简化MyBatis 的开发,提高效率。

快速上手

Spring Boot +MyBatis Plus

  1. 创建Maven工程

    /*
    在Spring Initializr 中勾选
    Lombok
    Spring Web
    Thymeleaf
    MySQL Driver
    */
    
  2. pom.xml引入 MyBatis Plus

    <dependency>
        <groupId>com.baomidou</groupId>
        <artifactId>mybatis-plus-boot-starter</artifactId>
        <version>3.5.1</version>
    </dependency>
    
  3. application.yml

    spring:
      datasource:
        driver-class-name: com.mysql.cj.jdbc.Driver
        username: root
        password: 123456
        url: jdbc:mysql://localhost:3306/test?useUnicode=true&characterEncoding=UTF-8&serverTimezone=GMT
    mybatis-plus:
      configuration:
        log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
    
    
  4. 创建实体类

    @Data
    public class Account implements Serializable {
        @TableId
        private Integer id;
        private String username;
        private String password;
        private String perms;
        private String role;
        @TableField(exist = false)
        private static final long serialVersionUID = 1L;
    }
    
  5. 创建Mapper接口

    @Repository
    public interface AccountMapper extends BaseMapper<Account> {
    
    }
    
  6. 启动类需要添加@MapperScan(“mapper所在的包”),否则无法加载Mppaer bean

    @SpringBootApplication
    @MapperScan("com.liner.springboot.springbootdemo05.mapper")
    public class Springbootdemo05Application {
    
        public static void main(String[] args) {
            SpringApplication.run(Springbootdemo05Application.class, args);
        }
    }
    
  7. 测试

    @SpringBootTest
    class AccountMapperTest {
    
        @Autowired
        private AccountMapper mapper;
    
        @Test
        void test(){
            mapper.selectList(null).forEach(System.out::println);
        }
    
    }
    

常用注解

  • @TableName:映射数据库的表名
@Data
@TableName (value = "user")
public class Account {
}
  • @TableId:设置主键映射,value映射主键字段名,type设置主键类型,主键的生成策略
@TableId(value = "uid" ,type = IdType.AUTO)
private Integer id;
描述
AUTO数据库自增
NONEMP set主键,雪花算法实现
INPUT需要开发者手动赋值
ASSIGN_IDMP分配ID,Long、Integer、String
ASSIGN_UUID分配UUID,Strinig

INPUT如果开发者没有手动赋值,则数据库通过自增的方式给主键赋值,如果开发者手动赋值,则存入该值。
AUTO默认就是数据库自增,开发者无需赋值。ASSIGN_ID MP自动赋值,雪花算法。
ASSIGN_UUID主键的数据类型必须是String,自动生成UUID进行赋值

  • @TableFieId:映射非主键字段, value映射字段名
    • exist表示是否为数据库字段false,如果实体类中的成员变量在数据库中没有对应的字段,则可以使用exist,VO、DTO
    • select表示是否查询该字段
    • fill表示是否自动填充,将对象存入数据库的时候,由MyBatis Plus自动给某些字段赋值,create_time、update_time
@TableField(value = "name",select = false)
private String title;

@TableField(fill = FieldFill.INSERT)
private Date createTime;
@TableField(fill = FieldFill.INSERT_UPDATE)
private Date updateTime;
@Component
public class MyMeta0bjectHandler implements Meta0bjectHandler {
    @0verride
    public void insertFill(Meta0bject meta0bject){
        this.setFieldValByName(fieldName: "createTime",new Date(),metaObject);
        this.setFieldValByName(fieldName: "updateTime",new Date(),metaObject);
    }
    @0verride
    public void updateFill(Meta0bject meta0bject){
        this.setFieldValByName(fieldName: "updateTime",new Date(),meta0bject);
    }
}
  • @Version:标记乐观锁,通过version字段来**保证数据的安全性**,
    • 当修改数据的时候,会以version 作为条件,当条件成立的时候才会修改成功。
    • 数据库表添加version字段,默认值为1
    • 实体类添加version成员变量,并且添加@Version
@Version
private Integer version;
@Configuration
public class MyBatisPlusconfig {
    @Bean
    public OptimisticLockerInterceptor optimisticLockerInterceptor(){
        return new OptimisticLockerInterceptor();
    }
}
  • @EnumValue:通用枚举类注解,将数据库字段映射成实体类的枚举类型成员变量
private statusEnum status;
public enum StatusEnum {
    WORK(code: 1, msg:"上班"),
    REST(code: 0, msg:"休息");

    StatusEnum(Integer code, String msg) {
        this.code = code;
        this.msg = msg;
    }

    @EnumValue
    private Integer code;
    private String msg;
}

//or

public enum StatusEnum implements IEnum<Integer> {
    WORK(code: 1, msg:"上班"),
    REST(code: 0, msg:"休息");

    private Integer code;
    private String msg;

    StatusEnum(Integer code, String msg) {
        this.code = code;
        this.msg = msg;
    }

    @Override
    public Integer getValue() {
        return this.code;
    }
}
#application.yml 中配置 枚举包扫描
type-enums-package: com.liner.mybatisplus.enums
  • @TableLogic:映射逻辑删除,不是真正的删除
@TableLogic
private Integer deleted;
#application.yml 中配置 
mybatis-plus:
  global-config:
    db-config:
      logic-not-delete-value: 0
      logic-delete-value: 1

CRUD

查询
//mapper.selectList(null);
QueryWrapper wrapper = new QueryWrapper();
//        Map<String,Object> map = new HashMap<>();
//        map.put("name","小红");
//        map.put("age",3);
//        wrapper.allEq(map);
//        wrapper.gt("age",2);
//        wrapper.ne("name","小红");
//        wrapper.ge("age",2);

//like '%小'
//        wrapper.likeLeft("name","小");
//like '小%'
//        wrapper.likeRight("name","小");

//inSQL
//        wrapper.inSql("id","select id from user where id < 10");
//        wrapper.inSql("age","select age from user where age > 3");

//        wrapper.orderByDesc("age");

//        wrapper.orderByAsc("age");
//        wrapper.having("id > 8");

mapper.selectList(wrapper).forEach(System.out::println);
//        System.out.println(mapper.selectById(7));
//        mapper.selectBatchIds(Arrays.asList(7,8,9)).forEach(System.out::println);

//Map 只能做等值判断,逻辑判断需要使用 Wrapper 来处理
//        Map<String,Object> map = new HashMap<>();
//        map.put("id",7);
//        mapper.selectByMap(map).forEach(System.out::println);

QueryWrapper wrapper = new QueryWrapper();
wrapper.eq("id",7);
        System.out.println(mapper.selectCount(wrapper));
//
//        //将查询的结果集封装到Map中
//        mapper.selectMaps(wrapper).forEach(System.out::println);
//        System.out.println("-------------------");
//        mapper.selectList(wrapper).forEach(System.out::println);

//分页查询
//        Page<User> page = new Page<>(2,2);
//        Page<User> result = mapper.selectPage(page,null);
//        System.out.println(result.getSize());
//        System.out.println(result.getTotal());
//        result.getRecords().forEach(System.out::println);

//        Page<Map<String,Object>> page = new Page<>(1,2);
//        mapper.selectMapsPage(page,null).getRecords().forEach(System.out::println);

//        mapper.selectObjs(null).forEach(System.out::println);


System.out.println(mapper.selectOne(wrapper));
自定义 SQL(多表关联查询)
@Data
public class ProductVO {
    private Integer category;
    private Integer count;
    private String description;
    private Integer userId;
    private String userName;
}
public interface UserMapper extends BaseMapper<User> {
    @Select("select p.*,u.name userName from product p,user u where p.user_id = u.id and u.id = #{id}")
    List<ProductVO> productList(Integer id);
}
添加
User user = new User();
user.setTitle("小明");
user.setAge(22);
mapper.insert(user);
System.out.println(user);
删除
//mapper.deleteById(1);
//        mapper.deleteBatchIds(Arrays.asList(7,8));
//        QueryWrapper wrapper = new QueryWrapper();
//        wrapper.eq("age",14);
//        mapper.delete(wrapper);

Map<String,Object> map = new HashMap<>();
map.put("id",10);
mapper.deleteByMap(map);
修改
//        //update ... version = 3 where version = 2
//        User user = mapper.selectById(7);
//        user.setTitle("一号");
//
//        //update ... version = 3 where version = 2
//        User user1 = mapper.selectById(7);
//        user1.setTitle("二号");
//
//        mapper.updateById(user1);
//        mapper.updateById(user);

User user = mapper.selectById(1);
user.setTitle("小红");
QueryWrapper wrapper = new QueryWrapper();
wrapper.eq("age",22);
mapper.update(user,wrapper);

MyBatisPlus 自动生成

根据数据表自动生成实体类、Mapper、Service、ServiceImpl、Controller

  • pom.xml 导入 MyBatis Plus Generator
<dependency>
    <groupId>com.baomidou</groupId>
    <artifactId>mybatis-plus-generator</artifactId>
    <version>3.5.1</version>
</dependency>

<dependency>
    <groupId>org.apache.velocity</groupId>
    <artifactId>velocity</artifactId>
    <version>1.7</version>
</dependency>

Velocity(默认)、Freemarker、Beetl

  • 启动类
public class Main {
    public static void main(String[] args) {
        //创建generator对象
        AutoGenerator autoGenerator = new AutoGenerator();
        //数据源
        DataSourceConfig dataSourceConfig = new DataSourceConfig();
        dataSourceConfig.setDbType(DbType.MYSQL);
        dataSourceConfig.setUrl("jdbc:mysql://ip:3306/test?useUnicode=true&characterEncoding=UTF-8");
        dataSourceConfig.setUsername("root");
        dataSourceConfig.setPassword("123456");
        dataSourceConfig.setDriverName("com.mysql.cj.jdbc.Driver");
        autoGenerator.setDataSource(dataSourceConfig);
        //全局配置
        GlobalConfig globalConfig = new GlobalConfig();
        globalConfig.setOutputDir(System.getProperty("user.dir")+"/src/main/java");
        globalConfig.setOpen(false);
        globalConfig.setAuthor("liner");
        globalConfig.setServiceName("%sService");
        autoGenerator.setGlobalConfig(globalConfig);
        //包信息
        PackageConfig packageConfig = new PackageConfig();
        packageConfig.setParent("com.liner.mybatisplus");
        packageConfig.setModuleName("generator");
        packageConfig.setController("controller");
        packageConfig.setService("service");
        packageConfig.setServiceImpl("service.impl");
        packageConfig.setMapper("mapper");
        packageConfig.setEntity("entity");
        autoGenerator.setPackageInfo(packageConfig);
        //配置策略
        StrategyConfig strategyConfig = new StrategyConfig();
        strategyConfig.setEntityLombokModel(true);
        strategyConfig.setNaming(NamingStrategy.underline_to_camel);
        strategyConfig.setColumnNaming(NamingStrategy.underline_to_camel);
        autoGenerator.setStrategy(strategyConfig);

        autoGenerator.execute();
    }
}
  • 2
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

今天你学Java了吗

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值