SSM-Spring(二)-数据类型转化、整合MyBatis、bean生命周期

数据类型转化Converter

spring内部封装了一个自定义日期转换格式的转化器

  1. Spring已经内置了基本的类型转换功能,比如
    1. String转int、String转Date(支持yyyy/MM/dd格式)
     <bean id="service" class="com.zh.service.PersonServiceImpl">
         <!--字符串自动转化为int-->
         <property name="age" value="25"/>
         <!--"2011/09/10"自动转化为Date-->
         <property name="birthday" value="2011/09/10"/>
     </bean>
    
  2. Spring默认的日期转化仅仅支持yyyy/MM/dd格式,那么如果我们要自己定义转化格式,该怎么办呢?

自定义Converter

  1. 自定义Converter类,实现Converter接口

     //继承Converter接口,String->Date
     public class DateConverter implements Converter<String, Date> {
         private List<String> formats;
        
         public void setFormats(List<String> formats) {
             this.formats = formats;
         }
        
         @Override
         public Date convert(String s) {
             //根据传过来的所有格式进行遍历
             for (String format : formats) {
                 try {
                     SimpleDateFormat fmt = new SimpleDateFormat(format);
                     return fmt.parse(s);
                 } catch (ParseException e) {
                     // e.printStackTrace();
                 }
             }
             return null;
         }
     }
    
  2. applicationContext.xml中注册

     <!--自动转化,但是注意必须是yyyy/MM/dd格式-->
    <!--    <bean id="person" class="com.zh.domain.Person" p:birthday="2011/09/10"/>-->
    
     <!--其他格式的转化,自定义转换器-->
     <bean id="person" class="com.zh.domain.Person" p:birthday="09_10_2111"/>
    
     <!-- 配置FactoryBean
     注意:id值必须是conversionService,因为这个id是给spring内部使用的,内部会根据这个指定的id来创建转换对象
     1. 创建Spring内置对象conversionService
     2. 给内置对象设置属性converters--所有自定义的转换器
     3. 将自定义转换器设置的所有格式进行设置
     -->
     <bean id="conversionService" class="org.springframework.context.support.ConversionServiceFactoryBean">
         <!-- 给上面对象设置属性-->
         <property name="converters">
             <set>
                 <!-- 设置自定义转化器-->
                 <bean class="com.zh.converter.DateConverter">
                     <property name="formats">
                         <list>
                             <!-- 同时兼容各种格式-->
                             <value>yyyy-MM-dd</value>
                             <value>MM_dd_yyyy</value>
                         </list>
                     </property>
                 </bean>
             </set>
         </property>
     </bean>
    
  3. 测试

     @Test
     public void test() {
         ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
         Person person = ctx.getBean("person1", Person.class);
         System.out.println(person);
     }
    

整合MyBatis

  1. 使用MyBatis的前期几个特点:
1.数据源的配置在核心配置文件mybatis-config.xml,如果使用第三方数据源要实现一个UnpooledDataSourceFactory的子类,并且实现其无参的构造方法,设置数据源属性
2.所有的mapper文件都需要在核心配置文件mybatis-config.xml一一导入
3.需要事先创建好SqlSessionFactory对象,封装一个工具类MyBatises,每次都需要调用openSession方法(close)

pom添加依赖

<dependencies>
    <!--spring-->
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-context</artifactId>
        <version>5.2.8.RELEASE</version>
    </dependency>
    <!--mybatis-->
    <dependency>
        <groupId>org.mybatis</groupId>
        <artifactId>mybatis</artifactId>
        <version>3.5.5</version>
    </dependency>
    <dependency>
        <groupId>com.alibaba</groupId>
        <artifactId>druid</artifactId>
        <version>1.2.3</version>
    </dependency>
    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
        <version>5.1.49</version>
    </dependency>

    <!--spring整合mybatis,需要添加的2个依赖-->
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-jdbc</artifactId>
        <version>5.2.6.RELEASE</version>
    </dependency>
    <dependency>
        <groupId>org.mybatis</groupId>
        <artifactId>mybatis-spring</artifactId>
        <version>2.0.6</version>
    </dependency>

    <!--单元测试-->
    <dependency>
        <groupId>junit</groupId>
        <artifactId>junit</artifactId>
        <version>4.13.2</version>
        <scope>test</scope>
    </dependency>
    <!--日志-->
    <dependency>
        <groupId>ch.qos.logback</groupId>
        <artifactId>logback-classic</artifactId>
        <version>1.2.3</version>
    </dependency>
</dependencies>

数据源设置

  1. 只需要在Spring的配置文件applicationContext.xml中配置即可

     <!-- 1. 导入配置文件db.properties -->
     <context:property-placeholder location="db.properties"/>
        
     <!-- 2. 定义数据源 -->
     <!-- 使用Spring内置的数据源(Spring的DriverManagerDataSource) -->
     <!-- <bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">-->
        
     <!-- 使用MyBatis内置的数据源(MyBatis的PooledDataSource) -->
     <!--<bean id="dataSource" class="org.apache.ibatis.datasource.pooled.PooledDataSource">-->
         <!--<property name="driver" value="${jdbc.driverClass}"/>-->
            
     <!-- 使用druid数据源(Druid) -->
     <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
         <property name="driverClassName" value="${jdbc.driverClass}"/>
         <property name="url" value="${jdbc.url}"/>
         <property name="username" value="${jdbc.username}"/>
         <property name="password" value="${jdbc.password}"/>
     </bean>
    
  2. 注意: 这里配置的连接池,默认会自动提交事务

SqlSessionFactoryBean配置

  1. 通过xml文件配置一个sqlSessionFactory的bean对象,类似原来的mybatis,用来操作mapper文件中的内容
  2. 主动设置数据源属性,之前的mybatis的SqlSessionFactory实例创建的时候会根据核心配置文件中配置的数据源内部自动设置。
  3. 设置mapperLocations属性,类似之前的核心配置文件中一一导入maper

     <!-- 创建SqlSessionFactory -->
     <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
         <!-- 1. 主动设置数据源 -->
         <property name="dataSource" ref="dataSource"/>
         <!-- 这个包底下的类会自动设置别名(一般是领域模型)
             这个包下面所有的别名就是类名
             比如com.zh.domain.Skill;别名是Skill
             设置完成之后具体的mapper文件比如skill.xml中就可以用别名Skill了
          -->
         <property name="typeAliasesPackage" value="com.zh.domain"/>
         <!-- 2. 映射文件的位置 -->
         <property name="mapperLocations">
             <array>
                 <value>mappers/*.xml</value>
             </array>
         </property>
     </bean>
    

MapperScannerConfigurer

  1. 封装一个全局的扫描对象,用来创建mybatis相关联的所有bean,该对象有spring内部自动创建
  2. 设置sqlSessionFactoryBeanName属性,获取sqlsession
  3. 设置basePackage,获取dao层
<!-- 扫描dao 
这个bean没有设置id值的原因是,spring在扫描到这个bean时直接创建对象,后序不会再通过id来使用这个对象,因此不需要id值
-->
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
    <!-- 设置MapperScannerConfigurer的sqlSessionFactoryBeanName属性值-->
    <property name="sqlSessionFactoryBeanName" value="sqlSessionFactory"/>
    <!-- 设置dao的包 ,通过上面的属性可拿到sqlsession,然后可通过getMapper方法实现所有dao接口对应的impl-->
    <property name="basePackage" value="com.zh.dao"/>
</bean>

使用

  1. 上面配置完之后,直接可以通过容器的getBean方法获取dao的代理对象,bean的id是dao类名的小驼峰形式

     //com.zh.dao.SkillDao的id是skillDao
     ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
     skillDao = ctx.getBean("skillDao", SkillDao.class);
    
  2. SkillTest如下:

     public class SkillTest {
         private ApplicationContext ctx;
         private SkillDao skillDao;
        
         @Before
         public void before() {
             ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
             //根据id创建dao的代理对象
             skillDao = ctx.getBean("skillDao", SkillDao.class);
         }
        
         @Test
         public void list() {
             List<Skill> skills = skillDao.list();
             System.out.println(skills);
         }
        
         @Test
         public void save() {
             System.out.println(skillDao.save(new Skill("123", 456)));
         }
     }
    
  3. Skill模型类如下

     package com.zh.domain;
    
     public class Skill {
         private Integer id;
         private String name;
         private Integer level;
        
         public Skill() {}
        
         public Skill(String name, Integer level) {
             this.name = name;
             this.level = level;
         }
        
         //属性get、set方法,省略。。。。
     }
    
  4. dao接口

     package com.zh.dao;
     import com.zh.domain.Skill;
     import org.apache.ibatis.annotations.Insert;
     import java.util.List;
     public interface SkillDao {
         //xml
         List<Skill> list();
        
         //注解
         @Insert("INSERT INTO skill(name, level) VALUES (#{name}, #{level})")
         boolean save(Skill skill);
     }
    
  5. resources/mappers中的skill.xml

     <?xml version="1.0" encoding="UTF-8" ?>
     <!DOCTYPE mapper
             PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
             "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
     <mapper namespace="com.zh.dao.SkillDao">
         <select id="list" resultType="Skill">
             SELECT * FROM skill
         </select>
     </mapper>
    

使用分析(applicationContext.xml文件从下往上分析)

  1. 创建MapperScannerConfigurer对象,然后根据属性依赖创建以下对象
    1. 该对象是扫描所有dao的接口类,然后拿到SqlSession,通过getMapper获取到所有dao接口类对应的impl实例
  2. 创建sqlSessionFactory对象
    1. 需要设置数据源
    2. 需要知道所有mapper映射文件的位置
  3. 设置所有dao的存放路径

整合分析

  1. 原来的mybaits的mybatis-config.xml中的信息都整合到了applicationContext.xml中
  2. mappers内容规则不变,dao层不需要实现impl
  3. mybaitis最终的使用方式是拿到SqlSession,然后通过session.getMapper()获取对应的接口创建出对应的dao的impl实例
  4. spring本质是通过MapperScannerConfigurer拿到SqlSession

IDEA直接操作数据库

  1. 点击IDEA右边列的Database工具菜单,展开
  2. 点击左上角的加号“+”,选择DataSource->MySQL
  3. 如果是第一次打开这个界面,在这个页面底部有download提示(download missing driver files),需要下载对应的驱动之后才能使用,点击下载即可
  4. 该界面可以设置数据库的名字name(假设命名为test)、地址(Host)、端口(Port)、用户名(User)、密码(password)、Database(需要连接的数据库,可以先不填,先连上Mysql服务器即可)
  5. 点击test Connect测试连接,如果没问题点击OK,就会连接数据库
  6. 会展示一个列表,有个schemas文件夹,下面是所有数据库的名称
  7. 如果之前Database没有指定哪个数据库,这里将显示一个数据
  8. 右击最上面根目录test->properties->选择Schemas->勾选All schemas,点击OK
  9. 则此次schemas文件夹下显示的是mysql服务器所有的数据库了
  10. 再次点击Database工具菜单折叠后,点击顶部的<schemas>(也可以在Database展开的情况下,点击左边“+”->Query Console,选择对应的数据库),然后在展开的列表里面选择你要操作的数据库,然后在里面写SQL语句,点击顶部的执行即可。

bean的生命周期

  1. 一个bean从出生到死亡, 经历的生命周期方法是

     1. 构造方法
     2. setter
         1. 如果通过属性注入肯定会调用setter方法,除非是通过构造方法注入
     3. BeanNameAware协议的setBeanName
         1. 获取当前bean 的名字,bean对象要实现BeanNameAware接口
         2. 让你知道一下bean的名字(id、name)
     4. ApplicationContextAware协议的setApplicationContext
         1. 获取当前IoC容器的名字,bean对象要实现ApplicationContextAware接口
         2. 让你知道一下你在哪个容器里面
     5. BeanPostProcessor协议的postProcessBeforeInitialization
         1. 初始化方法调用之前调用
     6. InitializingBean协议的afterPropertiesSet
         1. 监听对象初始化完成之后调用,效果跟init-method一样
         2.  构造、注入完毕之后调用①(初始化,加载资源)
     7. init-method
         1. bean标签属性,对象初始化"完成"的时候调用
         2. 初始化代表:构造方法、注入方法(setter)
         3. 构造、注入完毕之后调用②(初始化,加载资源)
     8. BeanPostProcessor的postProcessAfterInitialization
         1. 初始化方法调用之后调用
     9. 业务方法
         1. 拿到bean对象去做一些相关业务
     10. DisposableBean的destroy
         1. 监听bean对象销毁时调用,效果跟destory-method一样
         2. 销毁之前调用①(释放资源)
     11. destroy-method
         1. 对象呗销毁的时候调用
         2. 销毁之前调用②(释放资源)
    
  2. 当bean的scope为singleton,IoC容器关闭时,才会调用10、11方法
    1. 因为scope为singleton,此时在容器中是一个单例,归容器管理,因此容器销毁,单例也销毁
    2. 如果是prototype,则生命周期不归容器管理
  3. 参考文档:https://docs.spring.io/spring-framework/docs/current/spring-framework-reference/core.html#beans-factory-lifecycle

生命周期举例

  1. applicationContext.xml中注册类

     <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 http://www.springframework.org/schema/beans/spring-beans.xsd">
        
         <!--
         init-method:对象初始化"完成"的时候调用,初始化代表:构造方法、注入方法
         destroy-method:对象销毁的时候调用
         scope:默认为singleton,生命周期跟Ioc容器一致
         -->
         <bean id="userService"
               class="com.zh.service.impl.UserServiceImpl"
               p:age="20"
               init-method="init"
               destroy-method="dealloc"/>
        
         <!--监听所有的bean,相当于一个第三方监控对象,监听每个bean的初始化之前、之后的时刻-->
         <bean class="com.zh.processor.MyProcessor"/>
     </beans>
    
  2. service类

     //接口UserService
     public interface UserService {
         boolean login(String username, String password);
     }
        
     //实现
     public class UserServiceImpl implements
             UserService,
             BeanNameAware, ApplicationContextAware,
             InitializingBean, DisposableBean {
         private Integer age;
        
         //构造方法
         public UserServiceImpl() {
             System.out.println("01 - UserServiceImpl");
         }
        
         //setter方法,注入
         public void setAge(Integer age) {
             this.age = age;
             System.out.println("02 - setAge - " + age);
         }
        
         //业务方法
         @Override
         public boolean login(String username, String password) {
             System.out.println("09 - UserServiceImpl - login - " + username + "_" + password);
             return false;
         }
        
         public void init() {
             System.out.println("07 - init-method");
         }
        
         public void dealloc() {
             System.out.println("11 - destroy-method");
         }
        
         //BeanNameAware接口带的方法:获取bean的名字
         @Override
         public void setBeanName(String name) {
             System.out.println("03 - BeanNameAware - setBeanName - " + name);
         }
        
         //ApplicationContextAware接口带的方法,获取IoC容器的名称
         @Override
         public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
             System.out.println("04 - ApplicationContextAware - setApplicationContext - " + applicationContext);
         }
        
         //DisposableBean接口的方法,监听bean对象的销毁
         @Override
         public void destroy() throws Exception {
             System.out.println("10 - DisposableBean - destroy");
         }
        
         //InitializingBean接口的方法,监听bean对象初始化完毕之后调用
         @Override
         public void afterPropertiesSet() throws Exception {
             System.out.println("06 - InitializingBean - afterPropertiesSet");
         }
     }
    
  3. MyProcessor

     /**
      * com.zh.processor.MyProcessor,可以统一处理所有的Bean
      */
     public class MyProcessor implements BeanPostProcessor {
         @Override
         public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
        
             //对所有的bean对象做一个初始化前拦截
             System.out.println("05 - BeanPostProcessor - postProcessBeforeInitialization - " + beanName);
             return bean;
         }
        
         @Override
         public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
             //对所有的bean对象做一个初始化后拦截
             System.out.println("08 - BeanPostProcessor - postProcessAfterInitialization - " + beanName);
             return bean;
         }
     }
    
  4. UserTest

     @Test
     public void test() {
         // 创建容器
         ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
    
         UserService service = ctx.getBean("userService", UserService.class);
         service.login("123", "456");
    
         // 关闭容器
         ctx.close();
     }
    
Table of Contents