JavaSSM框架笔记

Spring

##常用依赖:

    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-webmvc</artifactId>
      <version>5.2.0.RELEASE</version>
      <scope>test</scope>
    </dependency>
    
    <dependency>
      <groupId>junit</groupId>
      <artifactId>junit</artifactId>
      <version>4.11</version>
      <scope>test</scope>
    </dependency>

IOC容器控制反转、AOP面向切面

IOC控制反转:使用beans.XML来配置后,使用ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");(获取Spring上下文对象)把对象都放在Spring中管理,使用时,直接调用beans.xml。

context.getBean("hello"),获取beans.xml配置中的id为hello对象
​
    <bean id="hello" class="com.spring.pojo.Hello">
        <property name="str" value="Spring"/>
    </bean>
    
property:属性设置 value:属性值
ref:为setXxxx()赋值

控制:谁来控制对象的创建,传统应用程序的对象是由程序本身控制创建的,使用Spring后,对象是有Spring来创建的。Spring容器就是beans.xml文件

反转:程序本身不创建对象,而变成被动的接收对象。

依赖注入:是利用Set方法来进行注入。

IOC是一种编程思想,由主动的编程变成被动的接收。

可以通过:ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");去浏览底层源码

IOC创建对象的方式

1.使用无参构造创建对象,默认!

2.使用有参构造创建对象,使用标签:constructor-arg,(1)下标赋值 (2)name来赋值

总结,Spring容器beans.xml中,运行的时候,容器里的每个对象都被实例了!

<!--import,导入其他beans.xml配置文件到一个总配置文件里,一般用于团队开发-->
<import resource="beans.xml"/>
​
​
   <!--
    id:是bean的唯一标识符,也就是相当于对象名
    class:bean的id所对应的全限定名:包名+类名
    name:别名,可以用逗号、分号,取多个别名
    -->
    
<bean id="UserDaoImpl" class="com.spring.dao.UserDaoImpl" name="UserDaoImpl2,u1,u2"></bean>
    <bean id="UserDaoMysqlImpl" class="com.spring.dao.UserDaoMysqlImpl"></bean>

依赖注入

1.构造器注入(拓展方式注入:C命名,有参、无参构造注入)

2.set注入(重点)(拓展方式注入:P命名)

依赖注入:Set注入!

依赖:bean对象的创建依赖于容器!

注入:bean对象中的所有属性,由容器来注入!

(C、P命名,需要导入xml约束)

public class Address {
    private String address;
​
    public String getAddress() {
        return address;
    }
​
    public void setAddress(String address) {
        this.address = address;
    }
}

public class Student {
    private String name;
    private Address address;
    private String[] books;
    private List<String > hobbys;
    private Map<String,String> card;
    private Set<String > games;
    private String wife;
    private Properties info;
​
}//上面的所有属性都get和set后,或者用Lombok注解

<!--多种属性值注入方法:-->
​
    <bean id="address" class="com.spring.Address">
        <property name="address" value="Address值注入成功!"></property>
    </bean>
​
    <bean id="student" class="com.spring.Student">
        <!--第一种,普通注入,value-->
        <property name="name" value="javaSpringName"></property>
​
        <!--第二种,bean注入,ref-->
        <property name="address" ref="address"></property>
​
        <!--第三种,数组注入,value-->
        <property name="books">
            <!--array数组闭合标签-->
            <array>
                <value>红楼梦</value>
                <value>水浒传</value>
                <value>西游记</value>
                <value>三国演义</value>
            </array>
        </property>
​
        <!--第四种List注入-->
        <property name="hobbys">
            <list>
                <value>打篮球</value>
                <value>敲代码</value>
                <value>听歌看片</value>
            </list>
        </property>
​
        <!--第五种Map注入-->
        <property name="card">
            <map>
                <entry key="Java" value="Java教程"></entry>
                <entry key="Python" value="Python教程"></entry>
            </map>
        </property>
​
        <!--第六种Set注入-->
        <property name="games">
            <set>
                <value></value>
                <value></value>
                <value></value>
            </set>
        </property>
​
        <!--空值 <property name="wife" value=""/>-->
        <!--null-->
        <property name="wife">
            <null></null>
        </property>
​
        <!--Properties-->
        <property name="info">
            <props>
                <!--<prop key="学号">值</prop>-->
                <prop key="学号">2021</prop>
                <prop key="性别">男</prop>
                <prop key="driver">jdbc...</prop>
                <prop key="url">http</prop>
                <prop key="username">root</prop>
                <prop key="password">123456</prop>
            </props>
        </property>
    </bean>

带有p名称空间的XML快捷方式

p-namespace允许您使用bean元素的属性(而不是嵌套的<property/>元素)来描述您的属性值。

Spring支持带有名称空间的可扩展配置格式,这些名称空间基于XML模式定义。本章讨论的beans配置格式在XML模式文档中定义。但是,p名称空间没有在XSD文件中定义,只存在于Spring的核心中。

以下示例显示了两个解析为相同结果的XML片段(第一个使用标准XML格式,第二个使用p命名空间):

<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"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
        https://www.springframework.org/schema/beans/spring-beans.xsd">
​
    <bean name="classic" class="com.example.ExampleBean">
        <property name="email" value="someone@somewhere.com"/>
    </bean>
​
    <bean name="p-namespace" class="com.example.ExampleBean"
        p:email="someone@somewhere.com"/>
</beans>

该示例显示了bean定义中名为email的p名称空间中的一个属性。这告诉Spring包含一个属性声明。如前所述,p-namespace没有模式定义,因此可以将属性名设置为属性名。

下一个示例包括另外两个bean定义,它们都引用了另一个bean:

<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"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
        https://www.springframework.org/schema/beans/spring-beans.xsd">
​
    <bean name="john-classic" class="com.example.Person">
        <property name="name" value="John Doe"/>
        <property name="spouse" ref="jane"/>
    </bean>
​
    <bean name="john-modern"
        class="com.example.Person"
        p:name="John Doe"
        p:spouse-ref="jane"/>
​
    <bean name="jane" class="com.example.Person">
        <property name="name" value="Jane Doe"/>
    </bean>
</beans>

此示例不仅包括使用p命名空间的属性值,还使用特殊格式声明属性引用。第一个bean定义使用<property name=“party”ref=“jane”/>来创建从bean john到bean jane的引用,而第二个bean定义使用p:party ref=“jane”作为属性来执行完全相同的操作。在本例中,property是属性名,而-ref部分表示这不是一个直接值,而是对另一个bean的引用。

单例模式

(Spring默认机制)

<bean...... scope="singleton"/>

原型模式

每次从容器中get的时候,都会产生一个新对象

<bean...... scope="prototype"/>

其余的request、session、application,只能在在web开发中使用

Bean的自动装配

  • 自动装配是Spring满足bean依赖一种方式!

  • Spring会在上下文中自动寻找,并自动给bean装配属性!

  1. 自动装配是Spring满足bean依赖一种方式!

  2. Spring会在上下文中自动寻找,并自动给bean装配属性!

  3. 隐式的自动装配bean【重要】

byName自动装配:

    <bean id="cat" class="Cat"/>
    <bean id="dog" class="Dog"/>
​
    <!--
    byName:会自动在容器上下文中查找,和自己对象set方法后面的值对应的bean的id,但是id只能是小写,才能自动装配
    -->
    <bean id="people" class="People" autowire="byName">
        <property name="name" value="帅气的东哥"/>
    </bean>

byType自动装配:

    <bean id="cat" class="Cat"/>
    <bean id="dog" class="Dog"/>
​
    <!--
    byType:则是查找和自己对象属性类型相同的bean,class里的类
    -->
    <bean id="people" class="People" autowire="byType">
        <property name="name" value="帅气的东哥"/>
    </bean>

小结:

  • byname的时候,需要保证所有bean的id唯一,并且这个bean需要和自动注入的属性的set方法的值一致!.

  • bytype的时候,需要保证所有bean的class唯一,并且这个bean需要和自动注入的属性的类型一致!

使用注解实现自动装配

使用注解须知:

  1. 导入约束

  2. 配置注解的支持:context:annotation-config/

    <?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:annotation-config/>
    ​
    ​
    </beans>

    注解自动装配:

    <?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:annotation-config/>
    ​
        <bean id="cat" class="Cat"/>
        <bean id="dog" class="Dog"/>
        <bean id="people" class="People"/>
    ​
    </beans>
    @Autowired
    private Cat cat;
    @Autowired
    private Dog dog;

    @Autowired

    直接在属性上使用,set方法等,而这注解像上面那样注解后,可以不需要public void set()方法,因为注解使用反射机制!但需要get方法!

    使用该注解,需要自动装配的属性在IOC(Spring)容器中存在且符合名字byName

  • 如果显示定义了Autowired 的required属性为false,说明这个对象可以为null,否则不允许为空

  • @Autowired(required = false)

@Qualifier(value="dog")

如多个重复,使用此注解,通过value查找具体的bean的id值

@Resource

首先通过bean的id (byName)查找,不对,就通过class (byType) 查找,两者都没有,才报错!

如果相同的class类,但是id有两个不同的,那么可以@Resource(name=” xx “)来指定具体的bean的id

使用注解开发

  1. bean

    使用注解开发,必须保证aop的包导入:org.springframework:spring-webmvc包,里面有aop,使用注解导入context约束,增加注解的支持!

    <!--指定要扫描的包,这个包下的注解就会生效-->
    <context:component-scan base-package="java"/>
    <!--注解驱动-->
    <context:annotation-config/>

  2. 属性如何注入

    //Componet:组成部分
    @Component
    public class People {
        private String name="java";
    }
    ​
    之后用   context.getBean("这里填类的小写名称",类.class);  进行获取
    ​
    或:
    @Component
    public class People {
        @Value("java")
        private String name;
    }

  3. 衍生的注解

    @Component有几个衍生注解,我们在web开发中,会按照mvc三层架构分层!

    • Dao 【@Repository】

    • Service 【@Service】

    • Servlet 【@Controller】

    • 这四个注解功能都是一样的,都是代表将某个类注册到Spring中,装配Bean

  4. 自动装配

  5. 作用域

    @Scope("单例/原型")

  6. 小结

    xml与注解

    • xml更加万能,适用于任何场合,复杂场合,维护简单方便

    • 注解:不是自己的类,使用不了,维护相对复杂

    xml与注解最佳实践:

    • xml用来管理bean

    • 注解只负责完成属性的注入

使用Java的方式配置Spring

我们现在要完全不使用Spring的xml配置了,全权交给Java来做!

JavaConfig是Spring的一个子项目,在Spring4之后成为核心功能

@Configuration
public class AppConfig {
​
    @Bean
    public MyService myService() {
        return new MyServiceImpl();
    }
}

与SpringXML文件在实例化ClassPathXmlApplicationContext,你可以用@Configuration类实例化时,类作为输入。AnnotationConfigApplicationContext。这允许完全不使用XML的Spring容器,如下例所示:

public static void main(String[] args) {
    ApplicationContext ctx = new AnnotationConfigApplicationContext(AppConfig.class);
    MyService myService = ctx.getBean(MyService.class);
    myService.doStuff();
}

代理模式

  • 为是什么学习代理模式?因为这是SpringAOP底层!

  • 这里通俗的说,代理模式,即:能够做扩展功能

静态代理

  • 抽象角色:一般使用接口或抽象类来解决

  • 真实角色:被代理的角色

  • 代理角色:代理真实角色,代理真实角色后,我们一般会做一些附属操作

  • 客户:访问代理对象的人

→ → (共同功能:租房)← ←

↑ ↑

客户——代理——————房东————↑

而租房就是一个功能接口,那么代理、房东,都要去实现(租房)这个接口!

然而代理,不仅能使用租房功能,还可以使用 收中介费功能、带客户去看房等,就是代理能够自己多出其他功能!

再次结论代理模式:房东只能出租房,但是有了代理,去代理房东后,不仅能出租房,还可以签合同,看房等诸多功能!但是,代理跟房东,需要一起去实现租房功能接口,然后代理的里面去set注入房东类,从而去代理房东,之后就可以不仅租房,还能签合同等等!然后客户类那里使用,需要代理和房东类,然后使用代理的注入功能,把房东注入进去,就实现了代理!这就是AOP后面的运用。

代理模式的好处:

  • 可以使真实角色的操作更加纯粹,不用去关注一些其他业务

  • 其他业务交给代理角色即可!实现了业务的分工!

  • 其他业务发生拓展的时候,方便集中管理!

缺点:

  • 一个真实角色就会产生一个代理角色,代码量会翻倍,开发效率低

动态代理

  • 动态代理和静态代理角色一样

  • 动态代理的类,是动态生成的,不是我们直接写好的!

  • 动态代理分为两大类:一个基于接口的动态代理,一个基于类的动态代理!

    • 基于接口:JDK动态代理

    • 基于类:cglib

    • java字节码实现:javassist

动态代理,了解两个类:Proxy(代理),invocationHandler(调用处理程序)

动态代理的好处:

  • 可以使真实角色(类似房东类)的操作更加纯粹!不用去关注一些其他公共业务

  • 公共也就就交给代理角色!实现了业务的分工!

  • 公共业务发生扩展的时候,方便集中管理!

  • 一个动态代理类代理的是一个接口,一般就是对应的一类业务

ProxyIH:实现了invocationHandler接口,即成为程序处理的类!(像生成代理类的工具类)

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
​
//此类就是你自己创建的一个代理类的处理程序(相当于一个工具类),他是动态的,ProxyIH,这个名字自定义命名!
//在这类里面,是动态引用被代理的类(也就是实体类)
public class ProxyIH implements InvocationHandler {
​
    //被代理的接口,必须要要有个参数变量(或说是对象)
    private 功能接口 功能接口;
​
    //上面一个是固定的代理接口,而下面这个是个万能接口,就是Object是所有的类,所以他能接收所有的类,从而达到动态代理
    private Object target;
​
    public void setTarget(Object target) {
        this.target = target;
    }
​
​
    //生成得到代理类
    public Object getProxy(){
        //第一个参数this.getClass().getClassLoader(),意思是调用现在此类
        //理解为,要去代理别人,那么自身也得用的上,所以第一个参数就调用了自己的这个类
        //然后第二个参数就是被代理的类,getInterfaces()就是获取这个类实现的接口!(*这里理解好*)
        //而最后一个参数是InvocationHandler,因为我们ProxyIH实现了InvocationHandler接口,所以ProxyIH就是InvocationHandler,所以第三个参数用this,即本身!
        return Proxy.newProxyInstance(this.getClass().getClassLoader(),target.getClass().getInterfaces(),this);
    }
​
​
    //处理代理实体类,然后实例(实例也就是实现的意思),并返回结果
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
​
        log(method.getName());
​
    //动态代理的本质就是使用反射机制
        Object result = method.invoke(target, args);
        return null;
    }
​
    /***
     * @description
     * 这个就是横切,在不修改原代码,插入其中
     * @param msg
     * @return void
     */
    public void log(String msg){
        System.out.println("增加了日志功能\n"+"输出了"+msg+"功能");
    }
}

客户类:

public class Client {
​
    public static void main(String[] args) {
        //真实角色,也就是接口的实现类(这里指"房东")
        ServiceImpl service = new ServiceImpl();
​
        //代理角色,不存在,即ProxyIH
        ProxyIH pih = new ProxyIH();
        pih.setTarget(service);//设置要代理的对象,就是功能接口实现的这个类(接口)
​
        //动态生成代理类,pih.getProxy();就是获取了我们需要代理的类,然后proxy为获取到的代理类的对象。
        
        //强转为:“功能接口”的类型,类型要为接口类,这是因为getProxy()里面的getInterfaces(),获取的就是接口,所以强转为接口类!
        功能接口 proxy = (功能接口) pih.getProxy();
        proxy.add();
    }
}

使用Spring实现AOP(横切)

需要导入一个依赖包

<dependency>
  <groupId>org.aspectj</groupId>
  <artifactId>aspectjweaver</artifactId>
  <version>1.9.7.M3</version>
</dependency>

————————————

    <bean id="Impl" class="com.spring.service.UserServiceImpl"/>
    <bean id="log" class="com.spring.log.Log"/>
​
    方式一:使用原生Spring API接口
    配置AOP
    <aop:config>
        <!--切入点:expression,表达式:execution(要执行的位置)-->
        <aop:pointcut id="pointcut" expression="execution(* com.spring.service.UserServiceImpl.*(..))"/>
​
        <!--执行环绕增加-->
        <!--log类切入到pointcut,也就是上面一个id为pointcut的类的里面-->
        <aop:advisor advice-ref="log" pointcut-ref="pointcut"/>
    </aop:config>
​
    方式二:自定义切入
    <bean id="DIY" class="com.spring.log.自定义切入点"/>
​
    <aop:config>
        <!--自定义切面,ref 要引用的类-->
        <aop:aspect ref="DIY">
            <!--切入点-->
            <aop:pointcut id="point" expression="execution(* com.spring.service.UserServiceImpl.*(..))"/>
            <!--通知-->
            <!--aop:before:在切入点执行前,运行此before切入-->
            <aop:before method="before" pointcut-ref="point"/>
            <!--aop:after:在切入点执行后,运行此after切入-->
            <aop:after method="after" pointcut-ref="point"/>
        </aop:aspect>
    </aop:config>
​

使用注解实现AOP

<!--方式三-->
<bean id="annnotationPointCut" class="com.spring.注解相关.AnnotationPointCut"/>
<!--开启注解支持   JDK(默认) cglib -->
<aop:aspectj-autoproxy/>

package com.spring.注解相关;
​
//方式三:使用注解方式实现AOP
​
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
​
@Aspect//注解-标注这个类是一个切面,要切入进去的类,下面的UserServiceImpl为切入点
​
public class AnnotationPointCut {
​
    @Before("execution(* com.spring.service.UserServiceImpl.*(..))")
    public void before(){
        System.out.println("======方法执行前======");
    }
    @After("execution(* com.spring.service.UserServiceImpl.*(..))")
    public void after(){
        System.out.println("======方法执行后======");
    }
​
    //在环绕增强中,我们可以给定一个参数,代表我们要获取的切入的点
    //此环绕增强优先运行!!!
    @Around("execution(* com.spring.service.UserServiceImpl.*(..))")
    public void around(ProceedingJoinPoint pj) throws Throwable {
        System.out.println("sss");
        pj.proceed();
    }
}

SpringMVC

什么是MVC:MVC是模型(Model)、视图(View)、控制器(Controller)的简写,是一种软件设计规范。

是将业务逻辑、数据、显示分离的方法来组织代码。

MVC主要作用是降低了视图与业务逻辑间的双向偶合。

MVC不是一种设计模式,MVC是一种架构模式。当然不同的MVC存在差异。

Model(模型):数据模型,提供要展示的数据,因此包含数据和行为,可以认为是领域模型或JavaBean组件(包含数据和行为),不过现在一般都分离开来:Value Object(数据Dao) 和 服务层(行为Service)。也就是模型提供了模型数据查询和模型数据的状态更新等功能,包括数据和业务。

View(视图):负责进行模型的展示,一般就是我们见到的用户界面,客户想看到的东西。

Controller(控制器):接收用户请求,委托给模型进行处理(状态改变),处理完毕后把返回的模型数据返回给视图,由视图负责展示。也就是说控制器做了个调度员的工作。

最典型的MVC就是JSP + servlet + javabean的模式。

职责分析:

Controller:控制器

  1. 取得表单数据

  2. 调用业务逻辑

  3. 转向指定的页面

Model:模型

  1. 业务逻辑

  2. 保存数据的状态

View:视图

  1. 显示页面

  1. 简要分析执行流程

    DispatcherServlet表示前置控制器,是整个SpringMVC的控制中心。用户发出请求,DispatcherServlet接收请求并拦截请求。

    我们假设请求的url为 : http://localhost:8080/SpringMVC/hello

    如上url拆分成三部分:

    http://localhost:8080服务器域名

    SpringMVC部署在服务器上的web站点

    hello表示控制器

    通过分析,如上url表示为:请求位于服务器localhost:8080上的SpringMVC站点的hello控制器。

  2. HandlerMapping为处理器映射。DispatcherServlet调用HandlerMapping,HandlerMapping根据请求url查找Handler。

  3. HandlerExecution表示具体的Handler,其主要作用是根据url查找控制器,如上url被查找控制器为:hello。

  4. HandlerExecution将解析后的信息传递给DispatcherServlet,如解析控制器映射等。

  5. HandlerAdapter表示处理器适配器,其按照特定的规则去执行Handler。

  6. Handler让具体的Controller执行。

  7. Controller将具体的执行信息返回给HandlerAdapter,如ModelAndView。

  8. HandlerAdapter将视图逻辑名或模型传递给DispatcherServlet。

  9. DispatcherServlet调用视图解析器(ViewResolver)来解析HandlerAdapter传递的逻辑视图名。

  10. 视图解析器将解析的逻辑视图名传给DispatcherServlet。

  11. DispatcherServlet根据视图解析器解析的视图结果,调用具体的视图。

  12. 最终视图呈现给用户。

导包:

<dependencies>
​
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-webmvc</artifactId>
        <version>5.3.8</version>
    </dependency>
​
    <dependency>
        <groupId>javax.servlet</groupId>
        <artifactId>javax.servlet-api</artifactId>
        <version>4.0.1</version>
        <scope>provided</scope>
    </dependency>
​
    <dependency>
        <groupId>javax.servlet</groupId>
        <artifactId>jstl</artifactId>
        <version>1.2</version>
    </dependency>
​
    <dependency>
        <groupId>javax.servlet</groupId>
        <artifactId>jsp-api</artifactId>
        <version>2.0</version>
        <scope>provided</scope>
    </dependency>
​
    <dependency>
        <groupId>org.apache.tomcat</groupId>
        <artifactId>tomcat-servlet-api</artifactId>
        <version>10.1.0-M1</version>
    </dependency>
​
</dependencies>

配置

DispatcherServlet

(前端分发控制器,所有的Servlet都经过它的处理,然后进行调度分发请求!)

springMVC:去web.xml里配置(注册)DispatcherServlet

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
         version="4.0">
​
    <!--配置DispatchServlet:这个是SpringMVC的核心,请求分发器,前端控制器-->
​
    <!--注册servlet:DispatcherServlet-->
    <servlet>
        <servlet-name>springmvc</servlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
​
        <!--绑定:通过初始化参数置顶SpringMVC配置文件的位置,进行关联-->
        <init-param>
            <param-name>contextConfigLocation</param-name>
            <param-value>classpath:springmvc-servlet.xml</param-value>
        </init-param>
        <!--启动级别:1-->
        <load-on-startup>1</load-on-startup>
​
    </servlet>
​
    <!--所有的请求都会被springmvc拦截-->
    <servlet-mapping>
        <servlet-name>springmvc</servlet-name>
        <url-pattern>/</url-pattern>
    </servlet-mapping>
​
</web-app>

springmvc-servlet.xml:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
​
    <!--处理器映射器-->
    <bean class="org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping"/>
    <!--处理器适配器-->
    <bean class="org.springframework.web.servlet.mvc.SimpleControllerHandlerAdapter"/>
    <!--视图解析器:DispatcherServlet给他的ModelAndView
    1.获取了ModelAndView的数据
    2.解析ModelAndView的视图名字
    3.拼接视图名字,找到对应的视图:/WEB-INF/jsp/xxx.jsp
    4.将数据渲染到这个视图上
    -->
    <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver" id="InternalResourceViewResolver">
        <!--前缀-->
        <property name="prefix" value="/WEB-INF/jsp/"/>
        <!--后缀-->
        <property name="suffix" value=".jsp"/>
    </bean>
​
    <!--Handler-->
    <bean id="/hello" class="com.spring.controller.HelloController"/>
​
​
​
</beans>

注解MVC

DispatcherServlet 依旧如上

注解springmvc-servlet.xml:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       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.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/mvc https://www.springframework.org/schema/mvc/spring-mvc.xsd">
​
​
    <!--自动扫描包,让指定下的注解生效,由IOC容器统一管理-->
    <context:component-scan base-package="com.spring.controller"/>
    <!--上面去走一遍包的扫描,但是不扫描静态资源(.css  .js  .html等),下面就是不扫描静态包-->
    <mvc:default-servlet-handler/>
    <!--相当于自动配置了处理器映射器、适配器-->
    <mvc:annotation-driven/>
​
    <!--视图解析器:DispatcherServlet给他的ModelAndView
    1.获取了ModelAndView的数据
    2.解析ModelAndView的视图名字
    3.拼接视图名字,找到对应的视图:/WEB-INF/jsp/xxx.jsp
    4.将数据渲染到这个视图上
    -->
    <bean id="InternalResourceViewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver">
        <!--前缀-->
        <property name="prefix" value="/WEB-INF/jsp/"/>
        <!--后缀-->
        <property name="suffix" value=".jsp"/>
    </bean>
​
</beans>

Controller控制器

import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
​
//@Controller注解的类会自动添加到Spring上下文中,被Spring接管
@Controller
public class HelloController {
    
    //映射访问路径(Servlet)
    @RequestMapping("/hello")
    public String hello(Model model){
        //封装数据
        model.addAttribute("msg","hello");
         return "hello"; //会被视图解析器处理,也就是跳到了hello.jsp
    }
}

由于Maven可能存在资源过滤的问题,我们将配置完善pom.xml

    <build>
        <resources>
            <resource>
                <directory>src/main/java</directory>
                <includes>
                    <include>**/*.properties</include>
                    <include>**/*.xml</include>
                </includes>
                <filtering>false</filtering>
            </resource>
            <resource>
                <directory>src/main/resources</directory>
                <includes>
                    <include>**/*.properties</include>
                    <include>**/*.xml</include>
                </includes>
                <filtering>false</filtering>
            </resource>
        </resources>
    </build>

可能遇到的问题:访问出现404,排查步骤:

  1. 查看控制台输出,看一下是不是缺少了什么jar包。

  2. 如果jar包存在,显示无法输出,就在IDEA的项目发布中,添加lib依赖!

  3. 重启Tomcat 即可解决!

小结

  1. 新建一个web项目

  2. 导入相关jar包

  3. 编写web.xml , 注册DispatcherServlet

  4. 编写springmvc配置文件

  5. 接下来就是去创建对应的控制类 , controller

  6. 最后完善前端视图和controller之间的对应

  7. 测试运行调试.

使用springMVC必须配置的三大件:

处理器映射器、处理器适配器、视图解析器

通常,我们只需要手动配置视图解析器,而处理器映射器和处理器适配器只需要开启注解驱动即可,而省去了大段的xml配置

RestFull风格

概念

Restful就是一个资源定位及资源操作的风格。不是标准也不是协议,只是一种风格。基于这个风格设计的软件可以更简洁,更有层次,更易于实现缓存等机制。

功能

资源:互联网所有的事物都可以被抽象为资源

资源操作:使用POST、DELETE、PUT、GET,使用不同方法对资源进行操作。

分别对应 添加、 删除、修改、查询。

传统方式操作资源 :通过不同的参数来实现不同的效果!方法单一,post 和 get

http://127.0.0.1/item/queryItem.action?id=1 查询,GET

http://127.0.0.1/item/saveItem.action 新增,POST

http://127.0.0.1/item/updateItem.action 更新,POST

http://127.0.0.1/item/deleteItem.action?id=1 删除,GET或POST

使用RESTful操作资源 :可以通过不同的请求方式来实现不同的效果!如下:请求地址一样,但是功能可以不同!

http://127.0.0.1/item/1 查询,GET

http://127.0.0.1/item 新增,POST

http://127.0.0.1/item 更新,PUT

http://127.0.0.1/item/1 删除,DELETE

在Spring MVC中可以使用 @PathVariable 注解,让方法参数的值对应绑定到一个URI模板变量上。

import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
​
//@Controller注解的类会自动添加到Spring上下文中,被Spring接管
@Controller
public class HelloController {
    /**
     * 原来http://localhost:8080/add?a=1&b=2
     * RestFull风格:http://localhost:8080/add/a/b
     */
    //映射访问路径(Servlet)
    @RequestMapping("/hello/{a}/{b}")
    public String hello(@PathVariable int a,@PathVariable int b, Model model){
        int s = a + b;
​
​
        //封装数据
        model.addAttribute("msg","a + b ="+s);
        return "hello"; //会被视图解析器处理,也就是跳到了hello.jsp
    }
}

使用method属性指定请求类型

用于约束请求的类型,可以收窄请求范围。指定请求谓词的类型如GET, POST, HEAD, OPTIONS, PUT, PATCH, DELETE, TRACE等

例子:

//映射访问路径,必须是POST请求
@RequestMapping(value = "/hello",method = {RequestMethod.POST})
public String index2(Model model){
    model.addAttribute("msg", "hello!");
    return "test";
}

小结

Spring MVC 的 @RequestMapping 注解能够处理 HTTP 请求的方法, 比如 GET, PUT, POST, DELETE 以及 PATCH。

所有的地址栏请求默认都会是 HTTP GET 类型的。

方法级别的注解变体有如下几个:组合注解

数据处理及跳转

结果跳转方式

通过SpringMVC来实现转发和重定向 - 无需视图解析器;

测试前,需要将视图解析器注释掉

@Controller
public class ResultSpringMVC {
    @RequestMapping("/rsm/t1")
    public String test1(){
        //转发
        return "/index.jsp";
    }
 
    @RequestMapping("/rsm/t2")
    public String test2(){
        //转发二
        return "forward:/index.jsp";
    }
 
    @RequestMapping("/rsm/t3")
    public String test3(){
        //重定向
        return "redirect:/index.jsp";
    }

通过SpringMVC来实现转发和重定向 - 有视图解析器;

@ResponseBody:不走视图解析器

重定向 , 不需要视图解析器 , 本质就是重新请求一个新地方嘛 , 所以注意路径问题.

可以重定向到另外一个请求实现 .

@Controller
public class ResultSpringMVC2 {
    @RequestMapping("/rsm2/t1")
    public String test1(){
        //转发
        return "test";
    }
 
    @RequestMapping("/rsm2/t2")
    public String test2(){
        //重定向
        return "redirect:/index.jsp";
        //return "redirect:hello.do"; //hello.do为另一个请求/
    }
 
}

数据处理

1、提交的域名称和处理方法的参数名一致

提交数据 : http://localhost:8080/hello?name=kuangshen

处理方法 :

@RequestMapping("/hello")
public String hello(String name){
    System.out.println(name);
    return "hello";
}

后台输出 : kuangshen

2、提交的域名称和处理方法的参数名不一致

提交数据 : http://localhost:8080/hello?username=kuangshen

处理方法 :

//@RequestParam("username") : username提交的域的名称 .
@RequestMapping("/hello")
public String hello(@RequestParam("username") String name){
    System.out.println(name);
    return "hello";
}

3、提交的是一个对象

要求提交的表单域和对象的属性名一致 , 参数使用对象即可

1、实体类

public class User {
    private int id;
    private String name;
    private int age;
    //构造
    //get/set
    //tostring()
}

2、提交数据 : http://localhost:8080/mvc04/user?name=kuangshen&id=1&age=15

3、处理方法 :

@RequestMapping("/user")
public String user(User user){
    System.out.println(user);
    return "hello";
}

后台输出 : User { id=1, name='kuangshen', age=15 }

说明:如果使用对象的话,前端传递的参数名和对象名必须一致,否则就是null。

数据显示到前端

第一种 : 通过ModelAndView

我们前面一直都是如此 . 就不过多解释

public class ControllerTest1 implements Controller {
 
    public ModelAndView handleRequest(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse) throws Exception {
        //返回一个模型视图对象
        ModelAndView mv = new ModelAndView();
        mv.addObject("msg","ControllerTest1");
        mv.setViewName("test");
        return mv;
    }
}

第二种 : 通过ModelMap

ModelMap

@RequestMapping("/hello")
public String hello(@RequestParam("username") String name, ModelMap model){
    //封装要显示到视图中的数据
    //相当于req.setAttribute("name",name);
    model.addAttribute("name",name);
    System.out.println(name);
    return "hello";
}

第三种 : 通过Model

Model

@RequestMapping("/ct2/hello")
public String hello(@RequestParam("username") String name, Model model){
    //封装要显示到视图中的数据
    //相当于req.setAttribute("name",name);
    model.addAttribute("msg",name);
    System.out.println(name);
    return "test";
}

对比

就对于新手而言简单来说使用区别就是:

Model 只有寥寥几个方法只适合用于储存数据,简化了新手对于Model对象的操作和理解;

ModelMap 继承了 LinkedMap ,除了实现了自身的一些方法,同样的继承 LinkedMap 的方法和特性;

ModelAndView 可以在储存数据的同时,可以进行设置返回的逻辑视图,进行控制展示层的跳转。

当然更多的以后开发考虑的更多的是性能和优化,就不能单单仅限于此的了解。

乱码问题

测试步骤:

1、我们可以在首页编写一个提交的表单

<form action="/e/t" method="post">
  <input type="text" name="name">
  <input type="submit">
</form>

2、后台编写对应的处理类

@Controller
public class Encoding {
    @RequestMapping("/e/t")
    public String test(Model model,String name){
        model.addAttribute("msg",name); //获取表单提交的值
        return "test"; //跳转到test页面显示输入的值
    }
}

3、输入中文测试,发现乱码

不得不说,乱码问题是在我们开发中十分常见的问题,也是让我们程序猿比较头大的问题!

以前乱码问题通过过滤器解决 , 而SpringMVC给我们提供了一个过滤器 , 可以在web.xml中配置 .

修改了xml文件需要重启服务器!

   <!--配置SpringMVC,中文乱码问题,过滤-->
    <filter>
        <filter-name>encoding</filter-name>
        <filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
        <init-param>
            <param-name>encoding</param-name>
            <param-value>utf-8</param-value>
        </init-param>
    </filter>
​
    <filter-mapping>
        <filter-name>encoding</filter-name>
        <url-pattern>/*</url-pattern>
    </filter-mapping>

但是我们发现 , 有些极端情况下.这个过滤器对get的支持不好 .(注:用上面情况,<url-pattern>/*</url-pattern>里面的*号必须要加上,否则没过滤JSP页面)

处理方法 :

1、修改tomcat配置文件 :设置编码!

<Connector URIEncoding="utf-8" port="8080" protocol="HTTP/1.1"
           connectionTimeout="20000"
           redirectPort="8443" />

2、自定义过滤器

package com.kuang.filter;
 
import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.util.Map;
 
/**
 * 解决get和post请求 全部乱码的过滤器
 */
public class GenericEncodingFilter implements Filter {
 
    @Override
    public void destroy() {
    }
 
    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
        //处理response的字符编码
        HttpServletResponse myResponse=(HttpServletResponse) response;
        myResponse.setContentType("text/html;charset=UTF-8");
 
        // 转型为与协议相关对象
        HttpServletRequest httpServletRequest = (HttpServletRequest) request;
        // 对request包装增强
        HttpServletRequest myrequest = new MyRequest(httpServletRequest);
        chain.doFilter(myrequest, response);
    }
 
    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
    }
 
}
 
//自定义request对象,HttpServletRequest的包装类
class MyRequest extends HttpServletRequestWrapper {
 
    private HttpServletRequest request;
    //是否编码的标记
    private boolean hasEncode;
    //定义一个可以传入HttpServletRequest对象的构造函数,以便对其进行装饰
    public MyRequest(HttpServletRequest request) {
        super(request);// super必须写
        this.request = request;
    }
 
    // 对需要增强方法 进行覆盖
    @Override
    public Map getParameterMap() {
        // 先获得请求方式
        String method = request.getMethod();
        if (method.equalsIgnoreCase("post")) {
            // post请求
            try {
                // 处理post乱码
                request.setCharacterEncoding("utf-8");
                return request.getParameterMap();
            } catch (UnsupportedEncodingException e) {
                e.printStackTrace();
            }
        } else if (method.equalsIgnoreCase("get")) {
            // get请求
            Map<String, String[]> parameterMap = request.getParameterMap();
            if (!hasEncode) { // 确保get手动编码逻辑只运行一次
                for (String parameterName : parameterMap.keySet()) {
                    String[] values = parameterMap.get(parameterName);
                    if (values != null) {
                        for (int i = 0; i < values.length; i++) {
                            try {
                                // 处理get乱码
                                values[i] = new String(values[i]
                                        .getBytes("ISO-8859-1"), "utf-8");
                            } catch (UnsupportedEncodingException e) {
                                e.printStackTrace();
                            }
                        }
                    }
                }
                hasEncode = true;
            }
            return parameterMap;
        }
        return super.getParameterMap();
    }
 
    //取一个值
    @Override
    public String getParameter(String name) {
        Map<String, String[]> parameterMap = getParameterMap();
        String[] values = parameterMap.get(name);
        if (values == null) {
            return null;
        }
        return values[0]; // 取回参数的第一个值
    }
 
    //取所有值
    @Override
    public String[] getParameterValues(String name) {
        Map<String, String[]> parameterMap = getParameterMap();
        String[] values = parameterMap.get(name);
        return values;
    }
}

这个也是我在网上找的一些大神写的,一般情况下,SpringMVC默认的乱码处理就已经能够很好的解决了!

然后在web.xml中配置这个过滤器即可!

乱码问题,需要平时多注意,在尽可能能设置编码的地方,都设置为统一编码 UTF-8!

Json交互处理

什么是JSON?

  • JSON(JavaScript Object Notation, JS 对象标记) 是一种轻量级的数据交换格式,目前使用特别广泛。

  • 采用完全独立于编程语言的文本格式来存储和表示数据。

  • 简洁和清晰的层次结构使得 JSON 成为理想的数据交换语言。

  • 易于人阅读和编写,同时也易于机器解析和生成,并有效地提升网络传输效率。

  • 在 JavaScript 语言中,一切都是对象。因此,任何JavaScript 支持的类型都可以通过 JSON 来表示,例如字符串、数字、对象、数组等。看看他的要求和语法格式:

  • 对象表示为键值对,数据由逗号分隔

  • 花括号保存对象

  • 方括号保存数组

JSON 键值对是用来保存 JavaScript 对象的一种方式,和 JavaScript 对象的写法也大同小异,键/值对组合中的键名写在前面并用双引号 "" 包裹,使用冒号 : 分隔,然后紧接着值:

{"name": "QinJiang"}
{"age": "3"}
{"sex": "男"}

很多人搞不清楚 JSON 和 JavaScript 对象的关系,甚至连谁是谁都不清楚。其实,可以这么理解:

JSON 是 JavaScript 对象的字符串表示法,它使用文本表示一个 JS 对象的信息,本质是一个字符串。

var obj = {a: 'Hello', b: 'World'}; //这是一个对象,注意键名也是可以使用引号包裹的
var json = '{"a": "Hello", "b": "World"}'; //这是一个 JSON 字符串,本质是一个字符串

JSON 和 JavaScript 对象互转

要实现从JSON字符串转换为JavaScript 对象,使用 JSON.parse() 方法:

var obj = JSON.parse('{"a": "Hello", "b": "World"}');
//结果是 {a: 'Hello', b: 'World'}

要实现从JavaScript 对象转换为JSON字符串,使用 JSON.stringify() 方法:

var json = JSON.stringify({a: 'Hello', b: 'World'});
//结果是 '{"a": "Hello", "b": "World"}'

代码测试

1、新建一个module ,springmvc-05-json , 添加web的支持

2、在web目录下新建一个 json-1.html , 编写测试内容

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>JSON</title>
</head>
<body>
 
<script type="text/javascript">
    //编写一个js的对象
    var user = {
        name:"小城里",
        age:3,
        sex:"男"
    };
    //将js对象转换成json字符串
    var str = JSON.stringify(user);
    console.log(str);
    
    //将json字符串转换为js对象
    var user2 = JSON.parse(str);
    console.log(user2.age,user2.name,user2.sex);
 
</script>
 
</body>
</html>

3、使用浏览器打开,查看控制台输出!

Controller返回JSON数据

Jackson

Jackson应该是目前比较好的json解s析工具了

当然工具不止这一个,比如还有阿里巴巴的 fastjson 等等。

我们这里使用Jackson,使用它需要导入它的jar包;

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

我们随便编写一个User的实体类,然后我们去编写我们的测试Controller;

package com.kuang.pojo;
 
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
 
//需要导入lombok
@Data
@AllArgsConstructor
@NoArgsConstructor
public class User {
 
    private String name;
    private int age;
    private String sex;
    
}

这里我们需要两个新东西,一个是@ResponseBody,一个是ObjectMapper对象,我们看下具体的用法

编写一个Controller;

@Controller
public class UserController {
 
    @RequestMapping("/json1")
    @ResponseBody
    public String json1() throws JsonProcessingException {
        //创建一个jackson的对象映射器,用来解析数据
        ObjectMapper mapper = new ObjectMapper();
        //创建一个对象
        User user = new User("中国1号", 3, "男");
        //将我们的对象解析成为json格式
        String str = mapper.writeValueAsString(user);
        //由于@ResponseBody注解,这里会将str转成json格式返回;十分方便
        return str;
    }
 
}

如果发现出现了乱码问题,我们需要设置一下他的编码格式为utf-8,以及它返回的类型;

通过@RequestMaping的produces属性来实现,修改下代码:

//produces:指定响应体返回类型和编码
@RequestMapping(value = "/json1",produces = "application/json;charset=utf-8")

【注意:使用json记得处理乱码问题】

JSON乱码统一解决

上一种方法比较麻烦,如果项目中有许多请求则每一个都要添加,可以通过Spring配置统一指定,这样就不用每次都去处理了!

我们可以在springmvc的配置文件上添加一段消息StringHttpMessageConverter转换配置!

<mvc:annotation-driven>
    <mvc:message-converters register-defaults="true">
        <bean class="org.springframework.http.converter.StringHttpMessageConverter">
            <constructor-arg value="UTF-8"/>
        </bean>
        <bean class="org.springframework.http.converter.json.MappingJackson2HttpMessageConverter">
            <property name="objectMapper">
                <bean class="org.springframework.http.converter.json.Jackson2ObjectMapperFactoryBean">
                    <property name="failOnEmptyBeans" value="false"/>
                </bean>
            </property>
        </bean>
    </mvc:message-converters>
</mvc:annotation-driven>

返回json字符串统一解决

在类上直接使用 @RestController ,这样子,里面所有的方法都只会返回 json 字符串了,不用再每一个都添加@ResponseBody !我们在前后端分离开发中,一般都使用 @RestController ,十分便捷!

@RestController
public class UserController {
 
    //produces:指定响应体返回类型和编码
    @RequestMapping(value = "/json1")
    public String json1() throws JsonProcessingException {
        //创建一个jackson的对象映射器,用来解析数据
        ObjectMapper mapper = new ObjectMapper();
        //创建一个对象
        User user = new User("秦疆1号", 3, "男");
        //将我们的对象解析成为json格式
        String str = mapper.writeValueAsString(user);
        //由于@ResponseBody注解,这里会将str转成json格式返回;十分方便
        return str;
    }
 
}

测试集合输出:

@RequestMapping("/json2")
public String json2() throws JsonProcessingException {
 
    //创建一个jackson的对象映射器,用来解析数据
    ObjectMapper mapper = new ObjectMapper();
    //创建一个对象
    User user1 = new User("中国1号", 3, "男");
    User user2 = new User("中国2号", 3, "男");
    User user3 = new User("中国3号", 3, "男");
    User user4 = new User("中国4号", 3, "男");
    List<User> list = new ArrayList<User>();
    list.add(user1);
    list.add(user2);
    list.add(user3);
    list.add(user4);
 
    //将我们的对象解析成为json格式
    String str = mapper.writeValueAsString(list);
    return str;
}

输出时间对象:

@RequestMapping("/json3")
public String json3() throws JsonProcessingException {
 
    ObjectMapper mapper = new ObjectMapper();
 
    //创建时间一个对象,java.util.Date
    Date date = new Date();
    //将我们的对象解析成为json格式
    String str = mapper.writeValueAsString(date);
    return str;
}

FastJson

fastjson.jar是阿里开发的一款专门用于Java开发的包,可以方便的实现json对象与JavaBean对象的转换,实现JavaBean对象与json字符串的转换,实现json对象与json字符串的转换。实现json的转换方法很多,最后的实现结果都是一样的。

fastjson 的 pom依赖!

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

fastjson 三个主要的类:

JSONObject 代表 json 对象

JSONObject实现了Map接口, 猜想 JSONObject底层操作是由Map实现的。

JSONObject对应json对象,通过各种形式的get()方法可以获取json对象中的数据,也可利用诸如size(),isEmpty()等方法获取"键:值"对的个数和判断是否为空。其本质是通过实现Map接口并调用接口中的方法完成的。

JSONArray 代表 json 对象数组

内部是有List接口中的方法来完成操作的。

JSON代表 JSONObject和JSONArray的转化

JSON类源码分析与使用

仔细观察这些方法,主要是实现json对象,json对象数组,javabean对象,json字符串之间的相互转化。

代码测试,我们新建一个FastJsonDemo 类

package com.kuang.controller;
 
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.kuang.pojo.User;
 
import java.util.ArrayList;
import java.util.List;
 
public class FastJsonDemo {
    public static void main(String[] args) {
        //创建一个对象
        User user1 = new User("中国1号", 3, "男");
        User user2 = new User("中国2号", 3, "男");
        User user3 = new User("中国3号", 3, "男");
        User user4 = new User("中国4号", 3, "男");
        List<User> list = new ArrayList<User>();
        list.add(user1);
        list.add(user2);
        list.add(user3);
        list.add(user4);
 
        System.out.println("*******Java对象 转 JSON字符串*******");
        String str1 = JSON.toJSONString(list);
        System.out.println("JSON.toJSONString(list)==>"+str1);
        String str2 = JSON.toJSONString(user1);
        System.out.println("JSON.toJSONString(user1)==>"+str2);
 
        System.out.println("\n****** JSON字符串 转 Java对象*******");
        User jp_user1=JSON.parseObject(str2,User.class);
        System.out.println("JSON.parseObject(str2,User.class)==>"+jp_user1);
 
        System.out.println("\n****** Java对象 转 JSON对象 ******");
        JSONObject jsonObject1 = (JSONObject) JSON.toJSON(user2);
        System.out.println("(JSONObject) JSON.toJSON(user2)==>"+jsonObject1.getString("name"));
 
        System.out.println("\n****** JSON对象 转 Java对象 ******");
        User to_java_user = JSON.toJavaObject(jsonObject1, User.class);
        System.out.println("JSON.toJavaObject(jsonObject1, User.class)==>"+to_java_user);
    }
}

这种工具类,我们只需要掌握使用就好了,在使用的时候在根据具体的业务去找对应的实现。和以前的commons-io那种工具包一样,拿来用就好了!

Json在我们数据传输中十分重要,一定要学会使用!

Mybatis

环境说明:

  • jdk 8 +

  • MySQL 5.7.19

  • maven-3.6.1

  • IDEA

学习前需要掌握:

  • JDBC

  • MySQL

  • Java 基础

  • Maven

  • Junit

什么是MyBatis

MyBatis 是一款优秀的持久层框架

MyBatis 避免了几乎所有的 JDBC 代码和手动设置参数以及获取结果集的过程

MyBatis 可以使用简单的 XML 或注解来配置和映射原生信息,将接口和 Java 的 实体类 【Plain Old Java Objects,普通的 Java对象】映射成数据库中的记录。

MyBatis 本是apache的一个开源项目ibatis, 2010年这个项目由apache 迁移到了google code,并且改名为MyBatis 。

2013年11月迁移到Github .

Mybatis官方文档 : mybatis – MyBatis 3 | 简介

GitHub : https://github.com/mybatis/mybatis-3

持久化

持久化是将程序数据在持久状态和瞬时状态间转换的机制。

即把数据(如内存中的对象)保存到可永久保存的存储设备中(如磁盘)。持久化的主要应用是将内存中的对象存储在数据库中,或者存储在磁盘文件中、XML数据文件中等等。

JDBC就是一种持久化机制。文件IO也是一种持久化机制。

在生活中 : 将鲜肉冷藏,吃的时候再解冻的方法也是。将水果做成罐头的方法也是。

为什么需要持久化服务呢?那是由于内存本身的缺陷引起的

内存断电后数据会丢失,但有一些对象是无论如何都不能丢失的,比如银行账号等,遗憾的是,人们还无法保证内存永不掉电。

内存过于昂贵,与硬盘、光盘等外存相比,内存的价格要高2~3个数量级,而且维持成本也高,至少需要一直供电吧。所以即使对象不需要永久保存,也会因为内存的容量限制不能一直呆在内存中,需要持久化来缓存到外存。

持久层

什么是持久层?

完成持久化工作的代码块 . ----> dao层 【DAO (Data Access Object) 数据访问对象】

大多数情况下特别是企业级应用,数据持久化往往也就意味着将内存中的数据保存到磁盘上加以固化,而持久化的实现过程则大多通过各种关系数据库来完成。

不过这里有一个字需要特别强调,也就是所谓的“层”。对于应用系统而言,数据持久功能大多是必不可少的组成部分。也就是说,我们的系统中,已经天然的具备了“持久层”概念?也许是,但也许实际情况并非如此。之所以要独立出一个“持久层”的概念,而不是“持久模块”,“持久单元”,也就意味着,我们的系统架构中,应该有一个相对独立的逻辑层面,专注于数据持久化逻辑的实现.

与系统其他部分相对而言,这个层面应该具有一个较为清晰和严格的逻辑边界。【说白了就是用来操作数据库存在的!】

为什么需要Mybatis

Mybatis就是帮助程序猿将数据存入数据库中 , 和从数据库中取数据 .

传统的jdbc操作 , 有很多重复代码块 .比如 : 数据取出时的封装 , 数据库的建立连接等等... , 通过框架可以减少重复代码,提高开发效率 .

MyBatis 是一个半自动化的ORM框架 (Object Relationship Mapping) -->对象关系映射

所有的事情,不用Mybatis依旧可以做到,只是用了它,所有实现会更加简单!技术没有高低之分,只有使用这个技术的人有高低之别

MyBatis的优点

简单易学:本身就很小且简单。没有任何第三方依赖,最简单安装只要两个jar文件+配置几个sql映射文件就可以了,易于学习,易于使用,通过文档和源代码,可以比较完全的掌握它的设计思路和实现。

灵活:mybatis不会对应用程序或者数据库的现有设计强加任何影响。sql写在xml里,便于统一管理和优化。通过sql语句可以满足操作数据库的所有需求。

解除sql与程序代码的耦合:通过提供DAO层,将业务逻辑和数据访问逻辑分离,使系统的设计更清晰,更易维护,更易单元测试。sql和代码的分离,提高了可维护性。

提供xml标签,支持编写动态sql。

.......

最重要的一点,使用的人多!公司需要!

使用相关步骤

1. 导入MyBatis相关jar包

<dependency>
    <groupId>org.mybatis</groupId>
    <artifactId>mybatis</artifactId>
    <version>3.5.2</version>
</dependency>
<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <version>5.1.47</version>
</dependency>

2. 编写MyBatis核心配置文件

<?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>
​
    <!--日志-->
    <!--<settings>-->
        <!--<setting name="logImpl" value="STDOUT_LOGGING"/>-->
    <!--</settings>-->
​
    <!--环境配置,连接的数据库,这里使用的是MySQL-->
    <environments default="mysql">
        <environment id="mysql">
            <!--指定事务管理的类型,这里简单使用Java的JDBC的提交和回滚设置-->
            <transactionManager type="JDBC"/>
            <!--dataSource 指连接源配置,POOLED是JDBC连接对象的数据源连接池的实现-->
            <dataSource type="POOLED">
                <property name="driver" value="com.mysql.cj.jdbc.Driver"></property>
                <property name="url" value="jdbc:mysql://localhost:3306/db_test?useUnicode=true&amp;characterEncoding=UTF-8&amp;useJDBCCompliantTimezoneShift=true&amp;useLegacyDatetimeCode=false&amp;serverTimezone=UTC"></property>
                <property name="username" value="root"></property>
                <property name="password" value="root"></property>
            </dataSource>
        </environment>
    </environments>
​
​
    <!--这是告诉Mybatis去哪找持久化类的映射文件,对于在src下的文件直接写文件名,
    如果在某包下,则要写明路径,如:com/mybatistest/config/User.xml-->
    <!--映射器有多种,推荐使用这种-->
    <!--特别注意,用class、packge方式注册映射器,需要接口跟Mapper文件同名!同名!同名!-->
    <mappers>
        <mapper resource="com/dao/UserMapper.xml"></mapper>
    </mappers>
</configuration>

3. 编写MyBatis工具类

package com.mybatis.utils;
​
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
​
import javax.annotation.Resource;
import java.io.IOException;
import java.io.InputStream;
​
//sqlSessionFactory -->sqlSession
public class MybatisUtils {
​
    public static SqlSessionFactory sqlSessionFactory;
​
    static {
        //固定,使用mybatis第一步,获取SqlSessionFactory对象
        try{
            String resource = "mybatis-config.xml";
            //获取文件流
            InputStream inputStream = Resources.getResourceAsStream(resource);
            //解析文件配置流
            sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
        }catch (IOException e){
            e.printStackTrace();
        }
    }
​
    public static SqlSession getSqlSession(){
        return sqlSessionFactory.openSession();
    }
}

4. 创建实体类

package com.pojo;
​
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
​
//1.实体
​
//Data:注解,需要导入Lombok依赖
@Data
public class User {
​
    private int id;
    private String name;
    private int age;
​
    public User(int id, String name, int age) {
        this.id = id;
        this.name = name;
        this.age = age;
    }
}

5. 编写Mapper接口类

package com.dao;
​
import com.pojo.User;
import java.util.List;
import java.util.Map;
​
//2.接口
public interface UserDao {
​
    //查询全部用户
    List<User> getUserList();
​
    //根据ID查询用户
    User getUserID(int id);
​
    //使用Map查询
    User getUserID2(Map map);
​
    //增加
    int addUser(User user);
​
    //更新
    int updateUser(User user);
​
    //删除
    int deleteUser(int id);
​
    //分页,Map<这里是键值对,所以必须要两个参数>
    List<User> getUserLimit(Map<String,Integer> map);
​
    //分页2
    List<User> getUserRowBounds();
}
​

6. 编写Mapper.xml配置文件

namespace 十分重要,不能写错!

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
​
<!--namespace命名绑定一个对应的Dao/Mapper接口-->
<mapper namespace="com.mybatis.dao.UserDao">
​
    <!--以下只有select查询语句,才有resultType-->
​
    <!--select查询语句,id对应com.pojo.User里的getUserList方法-->
    <select id="getUserList" resultType="com.mybatis.pojo.User">
        select * from tb_test
    </select>
​
    <!--根据ID查询用户-->
    <select id="getUserID"  parameterType="int" resultType="com.mybatis.pojo.User">
        select * from tb_test where id=#{id}
    </select>
​
    <!--字段参数过多,考虑使用map类型进行查询-->
    <!--根据Map查询-->
    <select id="getUserID2" parameterType="map" resultType="com.mybatis.pojo.User">
        select * from tb_test where id=#{id} and name = #{name}
    </select>
​
    <!--插入语句-->
    <insert id="addUser" parameterType="com.mybatis.pojo.User">
        insert into tb_test(id,name,age) values(#{id},#{name},#{age});
    </insert>
​
    <!--更新语句-->
    <update id="updateUser" parameterType="com.mybatis.pojo.User">
        update tb_test set name = #{name},age = #{age} where id=#{id};
    </update>
​
    <!--删除语句-->
    <delete id="deleteUser" parameterType="int">
        delete from tb_test where id=#{id};
    </delete>
​
​
<!--按照结果嵌套查询-->
    <select id="getStudent2" resultMap="StudentTeacher2">
        select s.id sid,s.name sname,t.name tname
        from student s,teacher t
        where s.tid=t.id;
    </select>
​
    <!--下面的type类型为全限定名,这里为取了全限定的别名为Student,因为实际是查询学生,所以是返回学生类型-->
    <resultMap id="StudentTeacher2" type="Student">
        <result property="id" column="sid"/>
        <result property="name" column="sname"/>
        <association property="teacher" javaType="Teacher">
            <result property="name" column="tname"/>
        </association>
    </resultMap>
​
​
    <!--分页-->
    <select id="getUserLimit" parameterType="map" resultType="com.pojo.User">
        select * from tb_test limit #{startIndex},#{pageSize};
    </select>
​
    <!--分页2-->
    <select id="getUserRowBounds" resultType="com.pojo.User">
        select * from tb_test
    </select>
​
​
<!--子查询-->
    <!--<select id="getStudent" resultMap="StudentTeacher">-->
        <!--select * from student-->
    <!--</select>-->
​
    <!--<resultMap id="StudentTeacher" type="Student">-->
        <!--<result property="id" column="id"/>-->
        <!--<result property="name" column="name"/>-->
        <!--&lt;!&ndash;复杂的属性,我们需要单独处理,对象:association,集合collection&ndash;&gt;-->
        <!--<association property="teacher" column="tid" javaType="Teacher" select="getTeacher"/>-->
    <!--</resultMap>-->
​
    <!--<select id="getTeacher" resultType="Teacher">-->
        <!--select * from teacher where id = #{id}-->
    <!--</select>-->
    
</mapper>
​
​
<!--3.接口实现类,只不过这里是变成Mapper.xml配置-->

7. 编写测试类

public class UserDaoTest {
​
    //增删改查需要提交事务,切记!
    
    @Test
    public void  test(){
        //第一步:获取SqlSession对象
        SqlSession sqlSession = MybatisUtils.getSqlSession();
        //方式一:getMapper
​
        try{
            UserDao mapper = sqlSession.getMapper(UserDao.class);
            List<User> userList = mapper.getUserList();
​
            for (User user : userList){
                System.out.println(user);
            }
        }catch(Exception e){
            e.printStackTrace();
        }finally{
            //因为sqlSession的实例不是线程安全的,所以不能被共享,最佳的作用域是方法里,所用完方法就关闭它。sqlSession.close()
            sqlSession.close();
        }
​
    }
​
    @Test
    public void testid(){
        //第一步:获取SqlSession对象
        SqlSession sqlSession = MybatisUtils.getSqlSession();
        //方式一:getMapper
​
        try{
            UserDao mapper = sqlSession.getMapper(UserDao.class);
            System.out.println(mapper.getUserID(1));
        }catch(Exception e){
            e.printStackTrace();
        }finally{
            sqlSession.close();
        }
    }
​
    @Test
    public void testid2(){
        //第一步:获取SqlSession对象
        SqlSession sqlSession = MybatisUtils.getSqlSession();
        //方式一:getMapper
​
        try{
            UserDao mapper = sqlSession.getMapper(UserDao.class);
            HashMap<Object, Object> map = new HashMap<Object, Object>();
            map.put("id",1);
            map.put("name","张三");
            System.out.println(mapper.getUserID2(map));
        }catch(Exception e){
            e.printStackTrace();
        }finally{
            sqlSession.close();
        }
    }
​
    @Test
    public void insert(){
        SqlSession sqlSession = MybatisUtils.getSqlSession();
​
        try{
            UserDao mapper = sqlSession.getMapper(UserDao.class);
            mapper.addUser(new User(4,"哈啊爱上大师",30));
            //提交事务
            sqlSession.commit();
            System.out.println("插入成功");
        }catch(Exception e){
            e.printStackTrace();
        }finally{
            sqlSession.close();
        }
​
​
​
    }
​
    @Test
    public void update(){
        SqlSession sqlSession = MybatisUtils.getSqlSession();
​
        try{
            UserDao mapper = sqlSession.getMapper(UserDao.class);
            mapper.updateUser(new User(3,"王五",13));
            sqlSession.commit();
            System.out.println("更新成功");
​
        }catch(Exception e){
            e.printStackTrace();
        }finally{
            sqlSession.close();
        }
    }
​
    @Test
    public void delete(){
        SqlSession sqlSession = MybatisUtils.getSqlSession();
​
        try{
            UserDao mapper = sqlSession.getMapper(UserDao.class);
            mapper.deleteUser(4);
            sqlSession.commit();
            System.out.println("删除成功");
​
        }catch(Exception e){
            e.printStackTrace();
        }finally{
            sqlSession.close();
        }
    }
​
​
    //分页查询Test
    @Test
    public void Limit(){
        SqlSession sqlSession = MybatisUtils.getSqlSession();
​
        try{
            UserDao mapper = sqlSession.getMapper(UserDao.class);
​
            //后面的Integer,必须使用Integer,如果使用String,那么语句就会变成'0','1',语法错误
            HashMap<String, Integer> map = new HashMap<String, Integer>();
            map.put("startIndex",0);
            map.put("pageSize",1);
​
            List<User> userLimit = mapper.getUserLimit(map);
            for (User user : userLimit){
                System.out.println(user);
            }
​
        }catch(Exception e){
            e.printStackTrace();
        }finally{
            sqlSession.close();
        }
    }
​
​
    //分页2
    @Test
    public void Limit2(){
        SqlSession sqlSession = MybatisUtils.getSqlSession();
​
        //RowBounds实现分页
        RowBounds rowBounds = new RowBounds(0, 2);
​
        //通过java代码层实现分页:RowBounds
        List<User> userList2 = sqlSession.selectList("com.dao.UserDao.getUserRowBounds",null,rowBounds);
​
        for (User user : userList2) {
            System.out.println(user);
        }
​
        sqlSession.close();
    }
​
}
​

8. Maven静态资源过滤问题

配置pom.xml

<resources>
    <resource>
        <directory>src/main/java</directory>
        <includes>
            <include>**/*.properties</include>
            <include>**/*.xml</include>
        </includes>
        <filtering>false</filtering>
    </resource>
    <resource>
        <directory>src/main/resources</directory>
        <includes>
            <include>**/*.properties</include>
            <include>**/*.xml</include>
        </includes>
        <filtering>false</filtering>
    </resource>
</resources>

万能Map

    //使用Map查询
    User getUserID2(Map map);

类取别名

<!--可以给实体类起别名,需要放在mybatis数据库配置文件中的<configuration>里面-->
    <typeAliases>
        <typeAlias type="com.pojo.User" alias="User"/>
        <!--使用注解扫描包,之后在实体类加注解,配合使用-->
        <package name="com.pojo"/>
    </typeAliases>
​
<!--给一个类同时使用两种方法取别名,默认方式好像是注解,具体自行测试-->

注解实体类起别名:

@Alias("sss")
public class book {
    xxxxxx
}

作用域(Scope)和生命周期

理解我们之前讨论过的不同作用域和生命周期类别是至关重要的,因为错误的使用会导致非常严重的并发问题。

对象生命周期和依赖注入框架

依赖注入框架可以创建线程安全的、基于事务的 SqlSession 和映射器,并将它们直接注入到你的 bean 中,因此可以直接忽略它们的生命周期。 如果对如何通过依赖注入框架使用 MyBatis 感兴趣,可以研究一下 MyBatis-Spring 或 MyBatis-Guice 两个子项目。

SqlSessionFactoryBuilder

这个类可以被实例化、使用和丢弃,一旦创建了 SqlSessionFactory,就不再需要它了。 因此 SqlSessionFactoryBuilder 实例的最佳作用域是方法作用域(也就是局部方法变量)。 你可以重用 SqlSessionFactoryBuilder 来创建多个 SqlSessionFactory 实例,但最好还是不要一直保留着它,以保证所有的 XML 解析资源可以被释放给更重要的事情。

SqlSessionFactory

SqlSessionFactory 一旦被创建就应该在应用的运行期间一直存在,没有任何理由丢弃它或重新创建另一个实例。 使用 SqlSessionFactory 的最佳实践是在应用运行期间不要重复创建多次,多次重建 SqlSessionFactory 被视为一种代码“坏习惯”。因此 SqlSessionFactory 的最佳作用域是应用作用域。 有很多方法可以做到,最简单的就是使用单例模式或者静态单例模式。

SqlSession

每个线程都应该有它自己的 SqlSession 实例。SqlSession 的实例不是线程安全的,因此是不能被共享的,所以它的最佳的作用域是请求或方法作用域。 绝对不能将 SqlSession 实例的引用放在一个类的静态域,甚至一个类的实例变量也不行。 也绝不能将 SqlSession 实例的引用放在任何类型的托管作用域中,比如 Servlet 框架中的 HttpSession。 如果你现在正在使用一种 Web 框架,考虑将 SqlSession 放在一个和 HTTP 请求相似的作用域中。 换句话说,每次收到 HTTP 请求,就可以打开一个 SqlSession,返回一个响应后,就关闭它。 这个关闭操作很重要,为了确保每次都能执行关闭操作,你应该把这个关闭操作放到 finally 块中。 下面的示例就是一个确保 SqlSession 关闭的标准模式:

try (SqlSession session = sqlSessionFactory.openSession()) {
  // 你的应用逻辑代码
}

在所有代码中都遵循这种使用模式,可以保证所有数据库资源都能被正确地关闭。

映射器实例

映射器是一些绑定映射语句的接口。映射器接口的实例是从 SqlSession 中获得的。虽然从技术层面上来讲,任何映射器实例的最大作用域与请求它们的 SqlSession 相同。但方法作用域才是映射器实例的最合适的作用域。 也就是说,映射器实例应该在调用它们的方法中被获取,使用完毕之后即可丢弃。 映射器实例并不需要被显式地关闭。尽管在整个请求作用域保留映射器实例不会有什么问题,但是你很快会发现,在这个作用域上管理太多像 SqlSession 的资源会让你忙不过来。 因此,最好将映射器放在方法作用域内。就像下面的例子一样:

try (SqlSession session = sqlSessionFactory.openSession()) {
  BlogMapper mapper = session.getMapper(BlogMapper.class);
  // 你的应用逻辑代码
}

解决属性名和字段名不一致问题

Tip:一般来说最好一致!

查询为null问题

数据库字段名:id,name,pwd

Java中的实体类设计:

public class User {
 
    private int id;  //id
    private String name;   //姓名
    private String password;   //密码和数据库不一样!
    
    //构造
    //set/get
    //toString()
}

接口

//根据id查询用户
User selectUserById(int id);

mapper映射文件

<select id="selectUserById" resultType="user">
    select * from user where id = #{id}
</select>

测试

@Test
public void testSelectUserById() {
    SqlSession session = MybatisUtils.getSession();  //获取SqlSession连接
    UserMapper mapper = session.getMapper(UserMapper.class);
    User user = mapper.selectUserById(1);
    System.out.println(user);
    session.close();
}

结果:

  • User{id=1, name='中国', password='null'}

  • 查询出来发现 password 为空 . 说明出现了问题!

分析:

  • select * from user where id = #{id} 可以看做

    select id,name,pwd from user where id = #{id}

  • mybatis会根据这些查询的列名(会将列名转化为小写,数据库不区分大小写) , 去对应的实体类中查找相应列名的set方法设值 , 由于找不到setPwd() , 所以password返回null ; 【自动映射】

解决方案

方案一:为列名指定别名 , 别名和java实体类的属性名一致 .

<select id="selectUserById" resultType="User">
    select id , name , pwd as password from user where id = #{id}
</select>

方案二:使用结果集映射->ResultMap 【推荐】

<resultMap id="UserMap" type="User">
    <!-- id为主键 -->
    <id column="id" property="id"/>
    <!-- column是数据库表的列名 , property是对应实体类的属性名 -->
    <result column="name" property="name"/>
    <result column="pwd" property="password"/>
</resultMap>
 
<select id="selectUserById" resultMap="UserMap">
    select id , name , pwd from user where id = #{id}
</select>

ResultMap

  • resultMap 元素是 MyBatis 中最重要最强大的元素。它可以让你从 90% 的 JDBC ResultSets 数据提取代码中解放出来。

  • 实际上,在为一些比如连接的复杂语句编写映射代码的时候,一份 resultMap 能够代替实现同等功能的长达数千行的代码。

  • ResultMap 的设计思想是,对于简单的语句根本不需要配置显式的结果映射,而对于复杂一点的语句只需要描述它们的关系就行了。

你已经见过简单映射语句的示例了,但并没有显式指定 resultMap。比如:

<select id="selectUserById" resultType="map">
select id , name , pwd
    from user
    where id = #{id}
</select>

上述语句只是简单地将所有的列映射到 HashMap 的键上,这由 resultType 属性指定。虽然在大部分情况下都够用,但是 HashMap 不是一个很好的模型。你的程序更可能会使用 JavaBean 或 POJO(Plain Old Java Objects,普通老式 Java 对象)作为模型。

ResultMap 最优秀的地方在于,虽然你已经对它相当了解了,但是根本就不需要显式地用到他们。

手动映射

1、返回值类型为resultMap

<select id="selectUserById" resultMap="UserMap">
    select id , name , pwd from user where id = #{id}
</select>

2、编写resultMap,实现手动映射!

<!--resultMap:结果集映射-->
<resultMap id="UserMap" type="User">
    <!-- id为主键 -->
    <id column="id" property="id"/>
    <!-- column是数据库表的列名 , property是对应实体类的属性名 -->
    <result column="name" property="name"/>
    <result column="pwd" property="password"/>
</resultMap>

日志工厂

Mybatis内置的日志工厂提供日志功能,具体的日志实现有以下几种工具:

  • SLF4J

  • Apache Commons Logging

  • Log4j 2

  • Log4j

  • JDK logging

具体选择哪个日志实现工具由MyBatis的内置日志工厂确定。它会使用最先找到的(按上文列举的顺序查找)。如果一个都未找到,日志功能就会被禁用。

标准日志实现

指定 MyBatis 应该使用哪个日志记录实现。如果此设置不存在,则会自动发现日志记录实现。

<settings>
        <setting name="logImpl" value="STDOUT_LOGGING"/>
</settings>

测试,可以看到控制台有大量的输出!我们可以通过这些输出来判断程序到底哪里出了Bug

Log4j

简介:

  • Log4j是Apache的一个开源项目

  • 通过使用Log4j,我们可以控制日志信息输送的目的地:控制台,文本,GUI组件....

  • 我们也可以控制每一条日志的输出格式;

  • 通过定义每一条日志信息的级别,我们能够更加细致地控制日志的生成过程。最令人感兴趣的就是,这些可以通过一个配置文件来灵活地进行配置,而不需要修改应用的代码。

使用步骤:

1、导入log4j的包

<dependency>
    <groupId>log4j</groupId>
    <artifactId>log4j</artifactId>
    <version>1.2.17</version>
</dependency>

2、配置文件编写

log4j.properties —> 放在resources目录下

#将等级为DEBUG的日志信息输出到console和file这两个目的地,console和file的定义在下面的代码
log4j.rootLogger=DEBUG,console,file
​
#控制台输出的相关设置
log4j.appender.console = org.apache.log4j.ConsoleAppender
log4j.appender.console.Target = System.out
log4j.appender.console.Threshold=DEBUG
log4j.appender.console.layout = org.apache.log4j.PatternLayout
log4j.appender.console.layout.ConversionPattern=[%c]-%m%n
​
#文件输出的相关设置
log4j.appender.file = org.apache.log4j.RollingFileAppender
#输出文件的位置为当前项目下(同src级)
log4j.appender.file.File=./log/LOG4J.log
log4j.appender.file.MaxFileSize=10mb
log4j.appender.file.Threshold=DEBUG
log4j.appender.file.layout=org.apache.log4j.PatternLayout
log4j.appender.file.layout.ConversionPattern=[%p][%d{yy-MM-dd}][%c]%m%n
​
#日志输出级别
log4j.logger.org.mybatis=DEBUG
log4j.logger.java.sql=DEBUG
log4j.logger.java.sql.Statement=DEBUG
log4j.logger.java.sql.ResultSet=DEBUG
log4j.logger.java.sql.PreparedStatement=DEBUG

3、setting设置日志实现

<!--放入数据库配置文件里的<configuration>里面-->
<settings>
    <setting name="logImpl" value="LOG4J"/>
</settings>

4、在程序中使用Log4j进行输出!

//注意导包:org.apache.log4j.Logger
static Logger logger = Logger.getLogger(MyTest.class);
 
@Test
public void selectUser() {
    logger.info("info:进入selectUser方法");
    logger.debug("debug:进入selectUser方法");
    logger.error("error: 进入selectUser方法");
    SqlSession session = MybatisUtils.getSession();
    UserMapper mapper = session.getMapper(UserMapper.class);
    List<User> users = mapper.selectUser();
    for (User user: users){
        System.out.println(user);
    }
    session.close();
}

5、测试,看控制台输出!

  • 使用Log4j 输出日志

  • 可以看到还生成了一个日志的文件 【需要修改file的日志级别】

6、注意点

如果Maven导入了log4j的jar包依赖,但是你没有使用log4j,也没有使用其他日志配置,也会有可能依旧出现3行来自log4j的报红,但是程序能依旧运行!如果不想看到这3行报红的提示,那就取Maven卸了log4j的日志jar包依赖!

分页

limit实现分页

#语法
SELECT * FROM table LIMIT stratIndex,pageSize
 
SELECT * FROM table LIMIT 5,10; // 检索记录行 6-15 
 
limit 3,2 //表示从第四行数据开始,取两条数据,第一个参数从下角标0开始
 
#为了检索从某一个偏移量到记录集的结束所有的记录行,可以指定第二个参数为 -1:   
SELECT * FROM table LIMIT 95,-1; // 检索记录行 96-last.  
 
#如果只给定一个参数,它表示返回最大的记录行数目:   
SELECT * FROM table LIMIT 5; //检索前 5 个记录行  
 
#换句话说,LIMIT n 等价于 LIMIT 0,n。 

分页Mapper查询语句

    <!--分页,推荐-->
    <select id="getUserLimit" parameterType="map" resultType="com.pojo.User">
        select * from tb_test limit #{startIndex},#{pageSize};
    </select>
​
    <!--RowBounds分页2,不太建议-->
    <select id="getUserRowBounds" resultType="com.pojo.User">
        select * from tb_test
    </select>

Java测试Test

//分页1查询Test
    @Test
    public void Limit(){
        SqlSession sqlSession = MybatisUtils.getSqlSession();
​
        try{
            UserDao mapper = sqlSession.getMapper(UserDao.class);
​
            //后面的Integer,必须使用Integer,如果使用String,那么语句就会变成'0','1',语法错误
            HashMap<String, Integer> map = new HashMap<String, Integer>();
            map.put("startIndex",0);
            map.put("pageSize",1);
​
            List<User> userLimit = mapper.getUserLimit(map);
            for (User user : userLimit){
                System.out.println(user);
            }
​
        }catch(Exception e){
            e.printStackTrace();
        }finally{
            sqlSession.close();
        }
    }
​
​
    //分页2
    @Test
    public void Limit2(){
        SqlSession sqlSession = MybatisUtils.getSqlSession();
​
        //RowBounds实现分页
        RowBounds rowBounds = new RowBounds(0, 2);
​
        //通过java代码层实现分页:RowBounds
        List<User> userList2 = sqlSession.selectList("com.dao.UserDao.getUserRowBounds",null,rowBounds);
​
        for (User user : userList2) {
            System.out.println(user);
        }
​
        sqlSession.close();
    }

使用注解开发

注解核心使用反射,底层动态代理

接口

public interface UserDao {
​
    //使用注解查询,注:简单语句可使用注解,复杂语句注解就会力不从心
    @Select("select * from tb_test")
    List<User> ZJselect();
​
    //方法存在多个参数,所有的参数前面必须加上@Param()注解,且取参数时,用Param里面的限定参数名。
    @Select("select * from tb_test where id=#{id}")
    User getUserID(@Param("id") int id);
​
    @Insert("insert into user(id,name,pwd) values(#{id},#{name},#{pwd})")
    int addUser(User user);
​
    @Update("update user set name=#{name},pwd=#{password} where id=#{id}")
    int upDate(User user);
​
    @Delete("delete from user where id=#{id}")
    int delete(@Param("id") int id);
​
}

绑定接口

在mybatis-config.xml中配置绑定接口

<configuration>
    
    <!--使用注解时绑定接口-->
    <mappers>
        <mapper class="com.mybatis.dao.UserDao"/>
    </mappers>
    
</configuration>

测试

Java的test测试

public class UserDaoTest {
​
    //使用注解查询,注:简单语句可使用注解,复杂语句注解就会力不从心
    @Test
    public void ZJselect(){
        SqlSession sqlSession = MybatisUtils.getSqlSession();
​
        UserDao mapper = sqlSession.getMapper(UserDao.class);
        List<User> users = mapper.ZJselect();
        for (User user : users) {
            System.out.println(user);
        }
​
        sqlSession.close();
​
    }
​
​
    @Test
    public void getUserID(){
        SqlSession sqlSession = MybatisUtils.getSqlSession();
​
        UserDao mapper = sqlSession.getMapper(UserDao.class);
        System.out.println(mapper.getUserID(1));
​
        sqlSession.close();
​
    }
    
    @Test
    public void getUserID(){
        SqlSession sqlSession = MybatisUtils.getSqlSession();
​
        UserDao mapper = sqlSession.getMapper(UserDao.class);
        System.out.println(mapper.addUser(new User(5,"中国","5")));
​
        sqlSession.close();
​
    }
    
    @Test
    public void addUser(){
        SqlSession sqlSession = MybatisUtils.getSqlSession();
​
        UserDao mapper = sqlSession.getMapper(UserDao.class);
        System.out.println(mapper.addUser(new User(1,"中国","1")));
​
        sqlSession.close();
​
    }
    
    
    @Test
    public void Update(){
        SqlSession sqlSession = MybatisUtils.getSqlSession();
​
        UserDao mapper = sqlSession.getMapper(UserDao.class);
        System.out.println(mapper.upDate(new User(5,"中国","5")));
​
        sqlSession.close();
​
    }
    
    @Test
    public void Delete(){
        SqlSession sqlSession = MybatisUtils.getSqlSession();
​
        UserDao mapper = sqlSession.getMapper(UserDao.class);
        System.out.println(mapper.delete(1));
        
        //这行提交事务,但是前面的return sqlSessionFactory.openSession(true);设置为true,那么就是自动提交事务,这里就不需要再手动添加代码了!
        
        sqlSession.close();
​
    }
    
}

多对一处理

数据库设计:

CREATE TABLE `teacher` (
  `id` INT(10) NOT NULL,
  `name` VARCHAR(30) DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=INNODB DEFAULT CHARSET=utf8
 
INSERT INTO teacher(`id`, `name`) VALUES (1, '秦老师');
 
CREATE TABLE `student` (
  `id` INT(10) NOT NULL,
  `name` VARCHAR(30) DEFAULT NULL,
  `tid` INT(10) DEFAULT NULL,
  PRIMARY KEY (`id`),
  KEY `fktid` (`tid`),
  CONSTRAINT `fktid` FOREIGN KEY (`tid`) REFERENCES `teacher` (`id`)
) ENGINE=INNODB DEFAULT CHARSET=utf8
 
 
INSERT INTO `student` (`id`, `name`, `tid`) VALUES ('1', '小明', '1');
INSERT INTO `student` (`id`, `name`, `tid`) VALUES ('2', '小红', '1');
INSERT INTO `student` (`id`, `name`, `tid`) VALUES ('3', '小张', '1');
INSERT INTO `student` (`id`, `name`, `tid`) VALUES ('4', '小李', '1');
INSERT INTO `student` (`id`, `name`, `tid`) VALUES ('5', '小王', '1');

学生类

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
​
@Data //GET,SET,ToString,有参,无参构造
@AllArgsConstructor
@NoArgsConstructor
public class Teacher {
    private int id;
    private String name;
}

老师类

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
​
@Data //GET,SET,ToString,有参,无参构造
@AllArgsConstructor
@NoArgsConstructor
public class Teacher {
    private int id;
    private String name;
}

接口

编写实体类对应的Mapper接口 【两个】

  • 无论有没有需求,都应该写上,以备后来之需!

public interface StudentMapper {
}
import org.apache.ibatis.annotations.Param;
import org.apache.ibatis.annotations.Select;
import pojo.Teacher;
​
public interface TeacherMapper {
​
    @Select("select * from Teacher where id = #{id}")
    Teacher getTeacher(@Param("id") int id);
}

对应Mapper.xml

StudentMapper.xml

嵌套查询,子查询

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="Dao.StudentMapper">
​
    <!--方式一:嵌套查询 -->
    
    <!--
    需求:获取所有学生及对应老师的信息
    思路:
        1. 获取所有学生的信息
        2. 根据获取的学生信息的老师ID->获取该老师的信息
        3. 思考问题,这样学生的结果集中应该包含老师,该如何处理呢,数据库中我们一般使用关联查询?
            1. 做一个结果集映射:StudentTeacher
            2. StudentTeacher结果集的类型为 Student
            3. 学生中老师的属性为teacher,对应数据库中为tid。
               多个 [1,...)学生关联一个老师=> 一对一,一对多
            4. 查看官网找到:association – 一个复杂类型的关联;使用它来处理关联查询
    -->
    <select id="getStudents" resultMap="StudentTeacher">
        select * from student
    </select>
    <resultMap id="StudentTeacher" type="Student">
        <!--association关联属性  property属性名 javaType属性类型 column在多的一方的表中的列名-->
        <!--这里的column='tid'就像外键,在学生表里是外键,老师的表里面是主键!-->
        <association property="teacher"  column="tid" javaType="Teacher" select="getTeacher"/>
        <!--association:对象,使用它 -->
        <!--collection:集合,使用它 -->
    </resultMap>
    <!--
    这里传递过来的id,只有一个属性的时候,下面可以写任何值
    association中column多参数配置:
        column="{key=value,key=value}"
        其实就是键值对的形式,key是传给下个sql的取值名称,value是片段一中sql查询的字段名。
    -->
    <select id="getTeacher" resultType="teacher">
        select * from teacher where id = #{id}
    </select>
​
</mapper>

按照结果嵌套查询

    <!--按照结果嵌套查询-->
    <!--注意:你有查询的,就有数据,没有查询的,例如去掉下面的t.id tid之后,那么老师的id就会没有数据,因为没有查询它-->
    <select id="getStudents2" resultMap="StudentTeacher2">
        select s.id sid,s.name sname,t.id tid,t.name tname
        from Student s,Teacher t
        where s.tid = t.id
    </select>
​
    <resultMap id="StudentTeacher2" type="pojo.Student">
        <result property="id" column="sid"/>
        <result property="name" column="sname"/>
        <association property="teacher" javaType="pojo.Teacher">
            <result property="id" column="tid"/>
            <result property="name" column="tname"/>
        </association>
    </resultMap>

TeacherMapper

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="Dao.TeacherMapper">
​
</mapper>

一对多处理

实体类

学生类

package pojo;
​
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
​
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Student {
    private int id;
    private String name;
​
    //一个老师
    private int tid;
}

老师类

package pojo;
​
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.ToString;
​
import java.util.List;
​
@Data //GET,SET,ToString,有参,无参构造
@AllArgsConstructor
@NoArgsConstructor
@ToString
public class Teacher {
    private int id;
    private String name;
​
    //一个老师有多个学生
    private List<Student> students;
}

接口

老师接口

import org.apache.ibatis.annotations.Param;
import org.apache.ibatis.annotations.Select;
import pojo.Teacher;
​
import java.util.List;
​
public interface TeacherMapper {
​
    //
    Teacher getTeacher(@Param("id") int id);
​
}

Mapper

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="Dao.TeacherMapper">
    
    <select id="getTeacher" resultMap="OneToMany" >
        select t.id tid,t.name tname,s.id sid,s.name sname
        from Teacher t,Student s
        where t.id = s.tid and t.id = #{id}
    </select>
​
    <resultMap id="OneToMany" type="pojo.Teacher">
        <result property="id" column="tid"/>
        <result property="name" column="tname"/>
        <collection property="students" javaType="List" ofType="pojo.Student">
            <result property="id" column="sid"/>
            <result property="name" column="sname"/>
        </collection>
    </resultMap>
​
</mapper>

主要学会运用:resultMap里的association和collection、还有javaType、ofType

动态SQL

动态 SQL 是 MyBatis 的强大特性之一。如果你使用过 JDBC 或其它类似的框架,你应该能理解根据不同条件拼接 SQL 语句有多痛苦,例如拼接时要确保不能忘记添加必要的空格,还要注意去掉列表最后一个列名的逗号。利用动态 SQL,可以彻底摆脱这种痛苦。

使用动态 SQL 并非一件易事,但借助可用于任何 SQL 映射语句中的强大的动态 SQL 语言,MyBatis 显著地提升了这一特性的易用性。

如果你之前用过 JSTL 或任何基于类 XML 语言的文本处理器,你对动态 SQL 元素可能会感觉似曾相识。在 MyBatis 之前的版本中,需要花时间了解大量的元素。借助功能强大的基于 OGNL 的表达式,MyBatis 3 替换了之前的大部分元素,大大精简了元素种类,现在要学习的元素种类比原来的一半还要少。

  • if

  • choose (when, otherwise)

  • trim (where, set)

  • foreach

if

使用动态 SQL 最常见情景是根据条件包含 where 子句的一部分。比如:

<select id="findActiveBlogWithTitleLike"
     resultType="Blog">
  SELECT * FROM BLOG
  WHERE state = ‘ACTIVE’
  <if test="title != null">
    AND title like #{title}
  </if>
</select>
​
<!-- <if test="title != null">:意思是,title这个传入的参数不为空的话,那么就执行,为空就不执行! -->

这条语句提供了可选的查找文本功能。如果不传入 “title”,那么所有处于 “ACTIVE” 状态的 BLOG 都会返回;如果传入了 “title” 参数,那么就会对 “title” 一列进行模糊查找并返回对应的 BLOG 结果(细心的读者可能会发现,“title” 的参数值需要包含查找掩码或通配符字符)。

如果希望通过 “title” 和 “author” 两个参数进行可选搜索该怎么办呢?首先,我想先将语句名称修改成更名副其实的名称;接下来,只需要加入另一个条件即可。

<select id="findActiveBlogLike"
     resultType="Blog">
  SELECT * FROM BLOG WHERE state = ‘ACTIVE’
  <if test="title != null">
    AND title like #{title}
  </if>
  <if test="author != null and author.name != null">
    AND author_name like #{author.name}
  </if>
</select>

choose、when、otherwise

有时候,我们不想使用所有的条件,而只是想从多个条件中选择一个使用。针对这种情况,MyBatis 提供了 choose 元素,它有点像 Java 中的 switch 语句。

还是上面的例子,但是策略变为:传入了 “title” 就按 “title” 查找,传入了 “author” 就按 “author” 查找的情形。若两者都没有传入,就返回标记为 featured 的 BLOG(这可能是管理员认为,与其返回大量的无意义随机 Blog,还不如返回一些由管理员精选的 Blog)。

<select id="findActiveBlogLike"
     resultType="Blog">
  SELECT * FROM BLOG WHERE state = ‘ACTIVE’
  <choose>
    <when test="title != null">
      AND title like #{title}
    </when>
    <when test="author != null and author.name != null">
      AND author_name like #{author.name}
    </when>
    <otherwise>
      AND featured = 1
    </otherwise>
  </choose>
</select>
​
<!-- 只要when里面第一个条件满足,就执行第一个,后面的条件即便满足也不会执行!-->
​
<!-- 
也可以这样:
<where>
 <choose>
    <when test="title != null">
      AND title like #{title}
    </when>
    <when test="author != null and author.name != null">
      AND author_name like #{author.name}
    </when>
    <otherwise>
      AND featured = 1
    </otherwise>
  </choose>
</where>
-->

trim、where、set

前面几个例子已经方便地解决了一个臭名昭著的动态 SQL 问题。现在回到之前的 “if” 示例,这次我们将 “state = ‘ACTIVE’” 设置成动态条件,看看会发生什么。

<select id="findActiveBlogLike"
     resultType="Blog">
  SELECT * FROM BLOG
  WHERE
  <if test="state != null">
    state = #{state}
  </if>
  <if test="title != null">
    AND title like #{title}
  </if>
  <if test="author != null and author.name != null">
    AND author_name like #{author.name}
  </if>
</select>

如果没有匹配的条件会怎么样?最终这条 SQL 会变成这样:

SELECT * FROM BLOG
WHERE

这会导致查询失败。如果匹配的只是第二个条件又会怎样?这条 SQL 会是这样:

SELECT * FROM BLOG
WHERE
AND title like ‘someTitle’

这个查询也会失败。这个问题不能简单地用条件元素来解决。这个问题是如此的难以解决,以至于解决过的人不会再想碰到这种问题。

MyBatis 有一个简单且适合大多数场景的解决办法。而在其他场景中,可以对其进行自定义以符合需求。而这,只需要一处简单的改动:

<select id="findActiveBlogLike"
     resultType="Blog">
  SELECT * FROM BLOG
  <where>
    <if test="state != null">
         state = #{state}
    </if>
    <if test="title != null">
        AND title like #{title}
    </if>
    <if test="author != null and author.name != null">
        AND author_name like #{author.name}
    </if>
  </where>
</select>

where 元素只会在子元素返回任何内容的情况下才插入 “WHERE” 子句。而且,若子句的开头为 “AND” 或 “OR”,where 元素也会将它们去除。

如果 where 元素与你期望的不太一样,你也可以通过自定义 trim 元素来定制 where 元素的功能。比如,和 where 元素等价的自定义 trim 元素为:

<trim prefix="WHERE" prefixOverrides="AND |OR ">
  ...
</trim>

prefixOverrides 属性会忽略通过管道符分隔的文本序列(注意此例中的空格是必要的)。上述例子会移除所有 prefixOverrides 属性中指定的内容,并且插入 prefix 属性中指定的内容。

用于动态更新语句的类似解决方案叫做 setset 元素可以用于动态包含需要更新的列,忽略其它不更新的列。比如:

<update id="updateAuthorIfNecessary">
  update Author
    <set>
      <if test="username != null">username=#{username},</if>
      <if test="password != null">password=#{password},</if>
      <if test="email != null">email=#{email},</if>
      <if test="bio != null">bio=#{bio}</if>
    </set>
  where id=#{id}
</update>

这个例子中,set 元素会动态地在行首插入 SET 关键字,并会删掉额外的逗号(这些逗号是在使用条件语句给列赋值时引入的)。

来看看与 set 元素等价的自定义 trim 元素吧:

<trim prefix="SET" suffixOverrides=",">
  ...
</trim>

注意,我们覆盖了后缀值设置,并且自定义了前缀值。

foreach

动态 SQL 的另一个常见使用场景是对集合进行遍历(尤其是在构建 IN 条件语句的时候)。比如:

<select id="selectPostIn" resultType="domain.blog.Post">
  SELECT *
  FROM POST P
  WHERE ID in
  <foreach item="item" index="index" collection="list"
      open="(" separator="," close=")">
        #{item}
  </foreach>
</select>
​
<!-- 这里的item是传入字段内容 -->

foreach 元素的功能非常强大,它允许你指定一个集合,声明可以在元素体内使用的集合项(item)和索引(index)变量。它也允许你指定开头与结尾的字符串以及集合项迭代之间的分隔符。这个元素也不会错误地添加多余的分隔符,看它多智能!

提示 你可以将任何可迭代对象(如 List、Set 等)、Map 对象或者数组对象作为集合参数传递给 foreach。当使用可迭代对象或者数组时,index 是当前迭代的序号,item 的值是本次迭代获取到的元素。当使用 Map 对象(或者 Map.Entry 对象的集合)时,index 是键,item 是值。

至此,我们已经完成了与 XML 配置及映射文件相关的讨论。下一章将详细探讨 Java API,以便你能充分利用已经创建的映射配置。

script

要在带注解的映射器接口类中使用动态 SQL,可以使用 script 元素。比如:

    @Update({"<script>",
      "update Author",
      "  <set>",
      "    <if test='username != null'>username=#{username},</if>",
      "    <if test='password != null'>password=#{password},</if>",
      "    <if test='email != null'>email=#{email},</if>",
      "    <if test='bio != null'>bio=#{bio}</if>",
      "  </set>",
      "where id=#{id}",
      "</script>"})
    void updateAuthorValues(Author author);

bind

bind 元素允许你在 OGNL 表达式以外创建一个变量,并将其绑定到当前的上下文。比如:

<select id="selectBlogsLike" resultType="Blog">
  <bind name="pattern" value="'%' + _parameter.getTitle() + '%'" />
  SELECT * FROM BLOG
  WHERE title LIKE #{pattern}
</select>

多数据库支持

如果配置了 databaseIdProvider,你就可以在动态代码中使用名为 “_databaseId” 的变量来为不同的数据库构建特定的语句。比如下面的例子:

<insert id="insert">
  <selectKey keyProperty="id" resultType="int" order="BEFORE">
    <if test="_databaseId == 'oracle'">
      select seq_users.nextval from dual
    </if>
    <if test="_databaseId == 'db2'">
      select nextval for seq_users from sysibm.sysdummy1"
    </if>
  </selectKey>
  insert into users values (#{id}, #{name})
</insert>

动态 SQL 中的插入脚本语言

MyBatis 从 3.2 版本开始支持插入脚本语言,这允许你插入一种语言驱动,并基于这种语言来编写动态 SQL 查询语句。

可以通过实现以下接口来插入一种语言:

public interface LanguageDriver {
  ParameterHandler createParameterHandler(MappedStatement mappedStatement, Object parameterObject, BoundSql boundSql);
  SqlSource createSqlSource(Configuration configuration, XNode script, Class<?> parameterType);
  SqlSource createSqlSource(Configuration configuration, String script, Class<?> parameterType);
}

实现自定义语言驱动后,你就可以在 mybatis-config.xml 文件中将它设置为默认语言:

<typeAliases>
  <typeAlias type="org.sample.MyLanguageDriver" alias="myLanguage"/>
</typeAliases>
<settings>
  <setting name="defaultScriptingLanguage" value="myLanguage"/>
</settings>

或者,你也可以使用 lang 属性为特定的语句指定语言:

<select id="selectBlog" lang="myLanguage">
  SELECT * FROM BLOG
</select>

或者,在你的 mapper 接口上添加 @Lang 注解:

public interface Mapper {
  @Lang(MyLanguageDriver.class)
  @Select("SELECT * FROM BLOG")
  List<Blog> selectBlog();
}

提示 可以使用 Apache Velocity 作为动态语言,更多细节请参考 MyBatis-Velocity 项目。

你前面看到的所有 xml 标签都由默认 MyBatis 语言提供,而它由语言驱动 org.apache.ibatis.scripting.xmltags.XmlLanguageDriver(别名为 xml)所提供。

SQL片段

有的时候,我们可能会将一些公共的部分抽取出来复用

相当于工具类,这里就是工具SQL语句

<sql id="if-title-author">
    <if test="title != null">
        title = #{title}
    </if>
    <if test="author != null">
        title = #{author}
    </if>
</sql>
​
​
<select id="selectBlog" lang="myLanguage">
  xxx
    xxx
    
    <include refid="if-title-author"></include>
    
    xxx
  xxx
</select>
​
​
​
​

缓存

MyBatis 内置了一个强大的事务性查询缓存机制,它可以非常方便地配置和定制。 为了使它更加强大而且易于配置,我们对 MyBatis 3 中的缓存实现进行了许多改进。

默认情况下,只启用了本地的会话缓存,它仅仅对一个会话中的数据进行缓存。 要启用全局的二级缓存,只需要在你的 SQL 映射文件中添加一行:

<cache/>

基本上就是这样。这个简单语句的效果如下:

  • 映射语句文件中的所有 select 语句的结果将会被缓存。

  • 映射语句文件中的所有 insert、update 和 delete 语句会刷新缓存。

  • 缓存会使用最近最少使用算法(LRU, Least Recently Used)算法来清除不需要的缓存。

  • 缓存不会定时进行刷新(也就是说,没有刷新间隔)。

  • 缓存会保存列表或对象(无论查询方法返回哪种)的 1024 个引用。

  • 缓存会被视为读/写缓存,这意味着获取到的对象并不是共享的,可以安全地被调用者修改,而不干扰其他调用者或线程所做的潜在修改。

提示 缓存只作用于 cache 标签所在的映射文件中的语句。如果你混合使用 Java API 和 XML 映射文件,在共用接口中的语句将不会被默认缓存。你需要使用 @CacheNamespaceRef 注解指定缓存作用域。

这些属性可以通过 cache 元素的属性来修改。比如:

<cache
  eviction="FIFO"
  flushInterval="60000"
  size="512"
  readOnly="true"/>

这个更高级的配置创建了一个 FIFO 缓存,每隔 60 秒刷新,最多可以存储结果对象或列表的 512 个引用,而且返回的对象被认为是只读的,因此对它们进行修改可能会在不同线程中的调用者产生冲突。

可用的清除策略有:

  • LRU – 最近最少使用:移除最长时间不被使用的对象。

  • FIFO – 先进先出:按对象进入缓存的顺序来移除它们。

  • SOFT – 软引用:基于垃圾回收器状态和软引用规则移除对象。

  • WEAK – 弱引用:更积极地基于垃圾收集器状态和弱引用规则移除对象。

默认的清除策略是 LRU。

flushInterval(刷新间隔)属性可以被设置为任意的正整数,设置的值应该是一个以毫秒为单位的合理时间量。 默认情况是不设置,也就是没有刷新间隔,缓存仅仅会在调用语句时刷新。

size(引用数目)属性可以被设置为任意正整数,要注意欲缓存对象的大小和运行环境中可用的内存资源。默认值是 1024。

readOnly(只读)属性可以被设置为 true 或 false。只读的缓存会给所有调用者返回缓存对象的相同实例。 因此这些对象不能被修改。这就提供了可观的性能提升。而可读写的缓存会(通过序列化)返回缓存对象的拷贝。 速度上会慢一些,但是更安全,因此默认值是 false。

提示 二级缓存是事务性的。这意味着,当 SqlSession 完成并提交时,或是完成并回滚,但没有执行 flushCache=true 的 insert/delete/update 语句时,缓存会获得更新。

使用自定义缓存

除了上述自定义缓存的方式,你也可以通过实现你自己的缓存,或为其他第三方缓存方案创建适配器,来完全覆盖缓存行为。

<cache type="com.domain.something.MyCustomCache"/>

这个示例展示了如何使用一个自定义的缓存实现。type 属性指定的类必须实现 org.apache.ibatis.cache.Cache 接口,且提供一个接受 String 参数作为 id 的构造器。 这个接口是 MyBatis 框架中许多复杂的接口之一,但是行为却非常简单。

public interface Cache {
  String getId();
  int getSize();
  void putObject(Object key, Object value);
  Object getObject(Object key);
  boolean hasKey(Object key);
  Object removeObject(Object key);
  void clear();
}

为了对你的缓存进行配置,只需要简单地在你的缓存实现中添加公有的 JavaBean 属性,然后通过 cache 元素传递属性值,例如,下面的例子将在你的缓存实现上调用一个名为 setCacheFile(String file) 的方法:

<cache type="com.domain.something.MyCustomCache">
  <property name="cacheFile" value="/tmp/my-custom-cache.tmp"/>
</cache>

你可以使用所有简单类型作为 JavaBean 属性的类型,MyBatis 会进行转换。 你也可以使用占位符(如 ${cache.file}),以便替换成在配置文件属性中定义的值。

从版本 3.4.2 开始,MyBatis 已经支持在所有属性设置完毕之后,调用一个初始化方法。 如果想要使用这个特性,请在你的自定义缓存类里实现 org.apache.ibatis.builder.InitializingObject 接口。

public interface InitializingObject {
  void initialize() throws Exception;
}

提示 上一节中对缓存的配置(如清除策略、可读或可读写等),不能应用于自定义缓存。

请注意,缓存的配置和缓存实例会被绑定到 SQL 映射文件的命名空间中。 因此,同一命名空间中的所有语句和缓存将通过命名空间绑定在一起。 每条语句可以自定义与缓存交互的方式,或将它们完全排除于缓存之外,这可以通过在每条语句上使用两个简单属性来达成。 默认情况下,语句会这样来配置:

<select ... flushCache="false" useCache="true"/>
<insert ... flushCache="true"/>
<update ... flushCache="true"/>
<delete ... flushCache="true"/>

鉴于这是默认行为,显然你永远不应该以这样的方式显式配置一条语句。但如果你想改变默认的行为,只需要设置 flushCache 和 useCache 属性。比如,某些情况下你可能希望特定 select 语句的结果排除于缓存之外,或希望一条 select 语句清空缓存。类似地,你可能希望某些 update 语句执行时不要刷新缓存。

cache-ref

回想一下上一节的内容,对某一命名空间的语句,只会使用该命名空间的缓存进行缓存或刷新。 但你可能会想要在多个命名空间中共享相同的缓存配置和实例。要实现这种需求,你可以使用 cache-ref 元素来引用另一个缓存。

<cache-ref namespace="com.someone.application.data.SomeMapper"/>

Tip:外界,用户等,使用缓存时,用户先请求的二级缓存,然后一级缓存

现在推荐Redis缓存

Spring-Mybatis

安装

要使用 MyBatis-Spring 模块,只需要在类路径下包含 mybatis-spring-2.0.6.jar 文件和相关依赖即可。

如果使用 Maven 作为构建工具,仅需要在 pom.xml 中加入以下代码即可:

<dependency>
  <groupId>org.mybatis</groupId>
  <artifactId>mybatis-spring</artifactId>
  <version>2.0.6</version>
</dependency>

这里做个简单的总结

在这之前,你需要一些前奏的配置

<?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">
​
    <!--DataSource:使用Spring的数据源替换原来Mybatis的数据源配置 c3p0  dbcp  druid-->
    <!--我们这样使用Spring提供的JDBC-->
    <bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
        <property name="driverClassName" value="com.mysql.cj.jdbc.Driver"/>
        <property name="url" value="jdbc:mysql://localhost:3306/db_test?useUnicode=true&amp;characterEncoding=UTF-8&amp;useJDBCCompliantTimezoneShift=true&amp;useLegacyDatetimeCode=false&amp;serverTimezone=UTC"/>
        <property name="username" value="root"/>
        <property name="password" value="root"/>
    </bean>
​
    <!--sqlSessionFactory-->
    <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
        <property name="dataSource" ref="dataSource" />
        <!--绑定Mybatis,配置Mybatis文件地址,之后可以联动使用-->
        <property name="configLocation" value="classpath*:mybatis-config.xml"/>
        <property name="mapperLocations" value="classpath*:sample/config/mappers/**/*.xml" />
    </bean>
​
    <!--SqlSessionTemplate:就是我们使用的sqlSession-->
    <bean id="sqlSession" class="org.mybatis.spring.SqlSessionTemplate">
        <!--只能使用构造器注入sqlSessionFactory,因为源码里没有set方法进行注入-->
        <constructor-arg index="0" ref="sqlSessionFactory" />
    </bean>
​
    <!--注入接口实现类的bean-->
    <bean id="userMapper" class="com.mapper.UserMapperImpl">
        <property name="sqlSession" ref="sqlSession"/>
    </bean>
​
</beans>

UserMapperImpl类:

public class UserMapperImpl implements UserMapper{
​
    //之前使用sqlSession来执行,现在使用SqlSessionTemplate
    private SqlSessionTemplate sqlSessionTemplate;
​
    public void setSqlSessionTemplate(SqlSessionTemplate sqlSessionTemplate) {
        this.sqlSessionTemplate = sqlSessionTemplate;
    }
​
    public List<User> selectUser(){
        UserMapper mapper = sqlSessionTemplate.getMapper(UserMapper.class);
        return mapper.selectUser();
    }
}

原型的Mybatis,需要写入以下代码行

SqlSession sqlSession = MybatisUtils.getSqlSession();

但是由Spring托管之后,按下面这样,注入 SqlSessionTemplate

<bean id="userDao" class="org.mybatis.spring.sample.dao.UserDaoImpl">
  <property name="sqlSession" ref="sqlSession" />
</bean>

SqlSessionTemplate 还有一个接收 ExecutorType 参数的构造方法。这允许你使用如下 Spring 配置来批量创建对象,例如批量创建一些 SqlSession:

<bean id="sqlSession" class="org.mybatis.spring.SqlSessionTemplate">
  <constructor-arg index="0" ref="sqlSessionFactory" />
  <constructor-arg index="1" value="BATCH" />
</bean>

之后你只需要使用Spring的ClassPathXmlApplication,context.getBean()来获取由Spring托管的SqlSession,再做查询

又或者,在上面的内容里,配置完后,使用context.getBean()获取userMapper,然后直接调用查询方法!

更多相关移步到(mybatis-spring –) MyBatis-Spring官方文档

事务

配置事务声明

<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
  <constructor-arg ref="dataSource" />
</bean>
​
<!--AOP-->
<!--配置事务通知-->
<tx:advice id="txAdvice" transaction-manager="transactionManager">
    <tx:attributes>
        <tx:method name="add" propagation="REQUIED"/>
        <tx:method name="delete" propagation="REQUIED"/>
        <tx:method name="update" propagation="REQUIED"/>
        <tx:method name="query" read-only="true"/>
        <tx:method name="add" propagation="REQUIED"/>
    </tx:attributes>
</tx:advice>
​
<!--配置事务切入-->
<aop:config>
    <aop:pointcut id="txPointCut" expression="execution(* com.xxx.xxx.*.*(..))"/>
    <aop:advisor advice-ref="txAdvice" pointcut-ref="txPointCut"/>
</aop:config>
<tx:jta-transaction-manager />
  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Java SSMSpring+SpringMVC+MyBatis)是一种基于Java语言的Web开发框架。学习这个框架的过程中,我深刻体会到它的强大和灵活性。 首先,Spring框架开发者提供了一个强大的IOC(Inversion of Control)容器,它能够管理和注入对象,减少了代码之间的耦合性。通过配置文件或注解,我们可以轻松地定义和获取各种对象,提高了代码的可维护性和可扩展性。 其次,SpringMVC框架是一种MVC(Model-View-Controller)设计模式的实现,它用于处理Web请求和响应。通过配置一个请求映射表和处理器,我们可以将请求分发给相应的控制器进行处理,并将处理结果返回给客户端。SpringMVC还提供了一些便捷的注解和标签,用于简化页面的渲染和参数的绑定。 最后,MyBatis是一种优秀的持久化框架,它能够将数据库操作与Java对象之间的映射简化为简单的配置。通过编写SQL映射文件和定义POJO(Plain Old Java Object)类,我们可以方便地进行数据库的增删改查操作,而无需编写冗长的SQL语句。 在学习Java SSM框架的过程中,我深入理解了软件开发过程中的MVC思想,并学会了如何利用SpringSpringMVC和MyBatis来实现一个完整的Web应用程序。通过不断的实践和调试,我逐渐培养了自己解决问题和调试代码的能力。 总结起来,学习Java SSM框架使我深入理解了软件开发的各个环节,并提升了我的编码能力和开发效率。我相信这些知识和经验将对我的职业发展和项目实施起到积极的促进作用。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值