JAVA入门基础_SSM的学习

目录

Mybatis的学习

Mybaits快速入门案例

Mybatis开发环境搭建

  • 下载Mybatis的jar包 或者 使用maven构建工具

  • 使用Mybatis最基础需要引入如下jar包

    • 连接数据库的驱动
    • mybatis的jar包
    • log4j日志(不使用mybaits也可以用,但是没有日志)

配置Mybaits的全局配置文件

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
        PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
    <!-- 引入外部配置文件,从根路径下找 -->
    <properties resource="jdbc.properties" />

    <!-- 配置数据库字段下划线与JAVA实体类的驼峰映射 -->
    <settings>
        <setting name="mapUnderscoreToCamelCase" value="true"/>
    </settings>

    <!-- 配置别名 -->
    <typeAliases>
        <package name="com.codestarts.pojo"/>
    </typeAliases>

    <!-- 配置一个个的环境,其中主要配置了事务管理的方式和数据源 -->
    <environments default="development">
        <environment id="development">
            <!--
                配置采用的事务管理方式,JDBC:编程式事务管理,
                MANAGER:啥也不干,让容器来管理事务的整个生命周期
            -->
            <transactionManager type="JDBC"/>
            <!--
                POOLED:采用数据库连接池的方式来获取连接
                UNPOOLED:每一个连接都直接通过访问数据库获得
                JNDI :上下文数据源引用(了解)
            -->
            <dataSource type="POOLED">
                <property name="driver" value="${driverClassName}"/>
                <property name="url" value="${url}"/>
                <property name="username" value="${username}"/>
                <property name="password" value="${password}"/>
            </dataSource>
        </environment>
        <!-- 可以配置多个,自行选择 -->
        <environment id="test">
            <transactionManager type="JDBC"/>
            <dataSource type="POOLED">
                <property name="driver" value="${driverClassName}"/>
                <property name="url" value="${url}"/>
                <property name="username" value="${username}"/>
                <property name="password" value="${password}"/>
            </dataSource>
        </environment>
    </environments>
    <!-- 配置*.xml映射文件-->
    <mappers>
        <!-- 配置单个的方式mappers/UserMapper.xml代表根路径下的mappers/UserMapper -->
        <!--<mapper resource="mappers/UserMapper.xml"></mapper>-->

        <!-- 
            配置mapper接口与mapper.xml所在的包,
            注意mapper接口和mapper.xml的名字必须一致 
            注意接口与mapper必须在同一个包下
        -->
        <package name="com.codestarts.mapper"/>
    </mappers>
</configuration>

配置一个Mapper接口文件

public interface UserMapper {
    int insert();
}

配置一个Mapper接口对应的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.codestarts.mapper.UserMapper">
    <insert id="insert">
        insert into user(name,gender,age) values('张三','女',15)
    </insert>
</mapper>

编写一个测试类

public class SqlSessionTest {
    public static void main(String[] args) throws IOException {
        // 获取到Mybatis配置文件
        InputStream is = Resources.getResourceAsStream("mybatis-config.xml");
        // 获取工厂
        SqlSessionFactoryBuilder sqlSessionFactoryBuilder = new SqlSessionFactoryBuilder();
        SqlSessionFactory build = sqlSessionFactoryBuilder.build(is);
        
        // 获得对应的Mapper接口,注意,这里得到的UserMapper是一个动态代理生成的实现类。
        SqlSession sqlSession = build.openSession();
        UserMapper mapper = sqlSession.getMapper(UserMapper.class);

        // 插入一条数据
        mapper.insert();
        // 提交与关闭
        sqlSession.commit();
        sqlSession.close();
    }
}

目录结构

image

Mybatis获取参数的6种方式

#{}获取数据与${}获取数据的区别

  • #{}相当于之前写JDBC时用的占位符,使用时传递参数会自动帮忙加上双引号

  • ${}则相当于字符串拼接

当需要获取的参数列表只有一个参数时

  • 在xml文件中可以通过 #{任意标识符}、${任意标识符}的方式获取

当需要获取的参数列表有多个参数时

  • 在xml文件中可以通过#{arg0 | param1}、#{arg1 | param2} 或者 ${arg0 | param1}、${arg1 | param2}

当需要获取的参数列表为一个Map集合时

  • 在xml文件中通过Map集合中的键值即可直接获取#{键值} 或者 ${键值}

当需要获取的参数列表为一个实体类时(掌握)

  • 在xml文件中使用#{}或者${}时,直接写属性名即可
    • 注意:属性名并不是指实体对象中的一个个成员变量,而是指一个个setter、getter

通过@Param注解的方式获取参数(掌握)

  • 在编写Mapper文件的接口时,在参数前加上注解:@Param("key")

  • 那么xml文件中就可以通过#{}或者${} 中写上该键值的方式直接获取到

如果接口仅仅只传递一个List时的处理

<insert id="insertList" parameterType="java.util.List">
		insert into student(name, age)
		values
		<foreach collection="list" item="item" index="index" separator="," open=")" close= ")">
			#{item.name} , #{item.age}
		</foreach>
	</insert>

添加日志功能

  • 引入日志jar包
<!-- log4j日志 -->
<dependency>
    <groupId>log4j</groupId>
    <artifactId>log4j</artifactId>
    <version>1.2.17</version>
</dependency>
  • 在根目录下添加log4j.xml文件,注意名称不能错
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE log4j:configuration SYSTEM "log4j.dtd">
<log4j:configuration xmlns:log4j="http://jakarta.apache.org/log4j/">
    <appender name="STDOUT" class="org.apache.log4j.ConsoleAppender">
        <param name="Encoding" value="UTF-8" />
        <layout class="org.apache.log4j.PatternLayout">
            <param name="ConversionPattern" value="%-5p %d{MM-dd HH:mm:ss,SSS}
%m (%F:%L) \n" />
        </layout>
    </appender>
    <logger name="java.sql">
        <level value="debug" />
    </logger>
    <logger name="org.apache.ibatis">
        <level value="info" />
    </logger>
    <root>
        <level value="debug" />
        <appender-ref ref="STDOUT" />
    </root>
</log4j:configuration>

Mybatis查询数据的各种情况

返回一个实体类对象和一个实体类对象的集合

  • xml文件中的select标签内将resultType写上需要映射的实体类全限定类名即可

  • Mapper接口

    // 获取一条记录
    User selectUserById(@Param("id") Integer id);
    
    // 获取多条记录
    List<User> selectList();
  • Mapper.xml文件,比较重要的式这个resultType
<select id="selectUserById" resultType="com.codestarts.pojo.User">
    select * from user where id = #{id}
</select>

<select id="selectList" resultType="com.codestarts.pojo.User">
    select * from user
</select>

返回单行单列

  • Mapper接口
    // 获取单行单列记录
    Integer selectCount();
  • Mapper.xml文件
    <select id="selectCount" resultType="java.lang.Integer">
        select count(1) from user
    </select>

查询的结果为Map时(2种情况)

返回一个List<Map<String, Object>>

  • Mapper接口如下
    List<Map<String, Object>> selectMap();
  • Mapper.xml如下
    <select id="selectMap" resultType="java.util.Map">
        select * from user
    </select>
  • 查询结果如下
[{gender=女, name=11三, id=1, age=15}, {gender=女, name=张三, id=2, age=15}, {gender=女, name=张三, id=3, age=15}]

返回一个Map<String, Map<String, Object>>

  • Mapper接口(使用了@MapKey注解,选择查询出的哪个字段作为外层Map的Key)
    @MapKey("id")
    Map<String, Object> selectMap02();
  • Mapper.xml(跟查询一个List<Map>是一致的)
    <select id="selectMap02" resultType="java.util.Map">
        select * from user
    </select>
  • 查询的结果直接打印
{1={gender=女, name=11三, id=1, age=15}, 2={gender=女, name=张三, id=2, age=15}, 3={gender=女, name=张三, id=3, age=15}}

特殊的Sql执行(有些地方必须使用SQL拼接${}完成)

模糊查询的四种方式

  • Mapper接口
    // 模糊查询的4种方式
    List<User> selectUserLikeName(@Param("name") String name);
    List<User> selectUserLikeName01(@Param("name") String name);
    List<User> selectUserLikeName02(@Param("name") String name);
    List<User> selectUserLikeName03(@Param("name") String name);
  • Mapper.xml文件
    <!--
        使用这种方式会报错,因为#{name}是个占位符,也就是我们之前JDBC所写的?,那么拼接后就变成了 '%?%'
        这个?占位符就被当成了一个字符串解析,因此报错
    -->
    <select id="selectUserLikeName" resultType="com.codestarts.pojo.User">
        select * from user where name like '%#{name}%'
    </select>

    <!-- 使用字符串拼接方式 -->
    <select id="selectUserLikeName01" resultType="com.codestarts.pojo.User">
        select * from user where name like '%${name}%'
    </select>

    <!-- 使用concat实现 -->
    <select id="selectUserLikeName02" resultType="com.codestarts.pojo.User">
        select * from user where name like concat('%',#{name},'%')
    </select>

    <!-- 推荐使用~~~ -->
    <select id="selectUserLikeName03" resultType="com.codestarts.pojo.User">
        select * from user where name like "%"#{name}"%"
    </select>

批量删除

  • Mapper接口
    int deleteBatch(@Param("ids") String ids);
  • Mapper.xml文件
    传入类似于:"5,1,6"这样的字符串,使用#{}就不行了
    <delete id="deleteBatch">
        delete from user where id in (${ids})
    </delete>

动态设置表名

  • Mapper接口
    List<Map<String, Object>> selectDynamicTableName(@Param("tableName") String tableName);
  • Mapper.xml文件
    <select id="selectDynamicTableName" resultType="java.util.Map">
        select * from ${tableName}
    </select>

新增记录时获取到自增的主键值

应用场景举例:添加一个班级,并且为该班级指定对应的学生。
  前提该要:mybaits的增删改方法返回的结果都是影响到的行数。因此如果想要得到自增的ID需要将该值赋值到插入的实体类的一个属性当中。

  • Mapper接口
    void insertuseGeneratedKey(User user);
  • Mapper.xml
    <!--
        useGeneratedKeys开启主键自增功能(JDBC的功能)
        keyProperty:需要映射到实体类的哪个字段(注意是实体类)
    -->
    <insert id="insertuseGeneratedKey" useGeneratedKeys="true" keyProperty="id">
        insert into user(name,gender,age) values(#{name},#{gender},#{age})
    </insert>
  • 测试一下
User user = new User(null, "王五", "女", 18);
mapper.insertuseGeneratedKey(user);
System.out.println(user);
// 得到的结果为:User(id=5, name=王五, gender=女, age=18),可以看出自增的id已经得到了

自定义映射ResultMap

为什么需要自定义ResultMap呢

  • (1)我们实体类中的属性名与数据库中的字段名不一定是相互对应的

  • (2)当我们查询时,出现一对多、多对多的情况时,单纯的字段映射已经无法搞定

定义并使用一个ResultMap

    <resultMap id="empResultMap" type="com.codestarts.pojo.Employee">
        <id column="id" property="id"></id>
        <result column="ename" property="eName"></result>
        <result column="job" property="job"></result>
        <result column="birth" property="birth"></result>
        <result column="sal" property="sal"></result>
        <result column="fk_dept_id" property="fkDeptId"></result>
    </resultMap>

    <select id="selectList" resultMap="empResultMap">
        select * from employee
    </select>

ResultMap处理多对一映射的3种方式

级联方式

  • Mapper接口
    /**
     * 多对一第一种方式,级联
     */
    List<Employee> selectResultMapManyForOne01();
  • Mapper.xml文件
    <resultMap id="empResultMap01" type="com.codestarts.pojo.Employee">
        <id column="id" property="id"></id>
        <result column="ename" property="eName"></result>
        <result column="job" property="job"></result>
        <result column="birth" property="birth"></result>
        <result column="sal" property="sal"></result>
        <result column="fk_dept_id" property="fkDeptId"></result>
        <result column="fk_dept_id" property="dept.id"></result>
        <result column="ename" property="dept.dname"></result>
        <result column="loc" property="dept.loc"></result>
    </resultMap>

    <select id="selectResultMapManyForOne01" resultMap="empResultMap01">
        select e.*,d.*
        from employee e left join dept d
        on e.fk_dept_id = d.id
    </select>

使用association处理映射关系

  • Mapper接口
    List<Employee> selectResultMapManyForOne02();
  • Mapper.xml文件
    <resultMap id="empResultMap02" type="com.codestarts.pojo.Employee">
        <id column="id" property="id"></id>
        <result column="ename" property="eName"></result>
        <result column="job" property="job"></result>
        <result column="birth" property="birth"></result>
        <result column="sal" property="sal"></result>
        <result column="fk_dept_id" property="fkDeptId"></result>
        <association property="dept" javaType="com.codestarts.pojo.Dept">
            <id property="id" column="fk_dept_id"></id>
            <result property="dname" column="dname"></result>
            <result property="loc" column="loc"></result>
        </association>
    </resultMap>
    <select id="selectResultMapManyForOne02" resultMap="empResultMap02">
        select e.*,d.*
        from employee e left join dept d
        on e.fk_dept_id = d.id
    </select>

使用分布查询处理映射关系

  • EmployeeMapper接口
    List<Employee>  selectResultMapManyForOne03();
  • EmployeeMapper.xml文件
    <resultMap id="empResultMap03" type="com.codestarts.pojo.Employee">
        <id column="id" property="id"></id>
        <result column="ename" property="eName"></result>
        <result column="job" property="job"></result>
        <result column="birth" property="birth"></result>
        <result column="sal" property="sal"></result>
        <result column="fk_dept_id" property="fkDeptId"></result>
        <association property="dept"
                     select="com.codestarts.mapper.DeptMapper.selectDeptById"
                     column="fk_dept_id"
        />
    </resultMap>

    <select id="selectResultMapManyForOne03" resultMap="empResultMap03">
        select * from employee
    </select>
  • DeptMapper接口
    Dept selectDeptById(@Param("deptId") Integer deptId);
  • DeptMapper.xml文件
    <select id="selectDeptById" resultType="com.codestarts.pojo.Dept">
        select * from dept where id = #{deptId}
    </select>

ResulMap处理一对多映射的2种方式

使用collection处理映射关系

  • Mapper接口
    List<Dept> selectResultMapOneForMany01();
  • Mapper.xml文件
    <resultMap id="deptResultMap01" type="com.codestarts.pojo.Dept">
        <id column="id" property="id" />
        <result column="dname" property="dname"/>
        <result column="loc" property="loc"/>
        <collection property="emps" ofType="com.codestarts.pojo.Employee">
            <id column="eid" property="id" />
            <result column="ename" property="eName"/>
            <result column="job" property="job"/>
            <result column="brith" property="brith"/>
            <result column="sal" property="sal"/>
            <result column="fk_dept_id" property="fkDeptId"/>
        </collection>
    </resultMap>

    <select id="selectResultMapOneForMany01" resultMap="deptResultMap01">
        select d.*,e.id eid,e.*
        from dept d left join employee e
        on d.id = e.fk_dept_id
    </select>

使用分布查询处理映射关系

  • DeptMapper接口
    List<Dept> selectResultMapOneForMany02();
  • DeptMapper.xml文件
    <resultMap id="deptResultMap02" type="com.codestarts.pojo.Dept">
        <id column="id" property="id" />
        <result column="dname" property="dname"/>
        <result column="loc" property="loc"/>
        <collection property="emps"
                    select="com.codestarts.mapper.EmployeeMapper.selectEmployeeByDeptId"
                    column="id">
        </collection>
    </resultMap>

    <select id="selectResultMapOneForMany02" resultMap="deptResultMap02">
        select d.*,e.id eid,e.*
        from dept d left join employee e
        on d.id = e.fk_dept_id
    </select>
  • EmployeeMapper接口
    List<Employee> selectEmployeeByDeptId(@Param("deptId") Integer deptId);
  • EmployeeMapper.xml文件
    <select id="selectEmployeeByDeptId" resultType="com.codestarts.pojo.Employee">
        select * from employee where fk_dept_id = #{deptId}
    </select>

Result小总结

association的作用

当需要映射的属性是一个实体类时,就可以考虑使用该标签。

  • 非分布查询时
    • property 实体的属性名称
    • javaType 该属性的全限定类名
  • 分布查询时
    • property 实体的属性名称
    • select 需要分布查询方法的唯一路径
    • column 需要传递给select的字段列
collection的作用

当需要映射的属性是一个集合时,可以考虑使用该标签

  • 非分布查询时

    • property 实体的属性名称
    • ofType 集合中的元素类型
  • 分布查询时(与association一致)

    • property 实体的属性名称
    • select 需要分布查询方法的唯一路径
    • column 需要传递给select的字段列
ResultMap实现分布查询时的延迟加载配置
  • 修改mybaits全局配置文件,添加如下setting
        <!--开启延迟加载功能-->
        <setting name="lazyLoadingEnabled" value="true"/>
        <!-- 关闭该功能后,才能进行按需加载,与lazyLoadingEnabled 配合使用-->
        <setting name="aggressiveLazyLoading" value="false"/>
  • 如果一个查询功能不想使用延迟加载,则需要在Mapper.xml文件的该方法的resultMap中的collectionassociation标签上添加fetchType="eager"

动态SQL

if进行模糊查询

    <select id="selectByCondition" resultType="com.codestarts.pojo.Employee">
        select * from employee where 1=1
        <if test="ename!=null and ename!=''">
            and ename like "%"#{ename}"%"
        </if>
        <if test="job!=null and job!=''">
            and job like "%"#{job}"%"
        </if>
        <if test="sal!=null and sal!=''">
            and sal like "%"#{sal}"%"
        </if>
    </select>

where配合if进行模糊查询

  • 当where中的条件有任意匹配时,会自动拼接上where条件

  • 会去除掉条件前多余的and(但是不会去掉条件后的and)

  • 当where中没有条件匹配时,该标签无效

    <select id="selectByCondition02" resultType="com.codestarts.pojo.Employee">
        select * from employee
        <where>
            <if test="ename!=null and ename!=''">
                ename like "%"#{ename}"%"
            </if>
            <if test="job!=null and job!=''">
                and job like "%"#{job}"%"
            </if>
            <if test="sal!=null and sal!=''">
                and sal like "%"#{sal}"%"
            </if>
        </where>
    </select>

trim

  • prefix/suffix 在SQL语句的前后添加上任意前缀或后缀

  • prefixOverrides / suffixOverrides 在SQL语句的前后删除任意前缀或后缀

choose、when、otherwise

  • 相当于 if .. else if .. else

  • 其中when至少需要出现一次

  • otherwise最多出现一次

  • 测试代码

    <select id="selectByCondition03" resultType="com.codestarts.pojo.Employee">
        select * from employee
        <where>
            <choose>
                <when test="ename!=null and ename!=''">
                    ename like "%"#{ename}"%"
                </when>
                <when test="job!=null and job!=''">
                    job like "%"#{job}"%"
                </when>
            </choose>
        </where>
    </select>

foreach(重要)

新增多条记录(使用了集合)
  • Mapper接口
    void insertBatchRecord(@Param("emps") List<Employee> emps);
  • Mapper.xml文件
    <insert id="insertBatchRecord">
        insert into employee values
        <foreach collection="emps" item="emp" separator=",">
            (null, #{emp.ename}, #{emp.job}, #{emp.brith}, #{emp.sal}, #{emp.fkDeptId})
        </foreach>
    </insert>
删除多条记录(使用了数组)
  • Mapper接口
    void deleteBatchRecord(@Param("ids") Integer[] ids);
  • Mapper.xml文件
    <delete id="deleteBatchRecord">
        delete from employee where id in
        <foreach collection="ids" item="id" separator="," open="(" close=")">
            #{id}
        </foreach>
    </delete>

sql(片段)

作用:将重复的SQL代码统一的封装起来

    <sql id="allFiled">
        id, ename, job, brith, sal, fk_dept_id
    </sql>
	
    <select id="selectList" resultType="com.codestarts.pojo.Employee">
        select <include refid="allFiled"/>  from employee
    </select>

MyBatis缓存

一级缓存

  • SqlSession级别,仅在同一个SqlSession有效,默认开启

  • 失效条件
    (1)使用同一条SQL语句查询时中间穿插了增删改操作
    (2) 清空了SqlSession的缓存sqlSession.clearCache()

二级缓存

缓存的生效条件
  • 在mybatis全局配置中开启二级缓存功能
    <setting name="cacheEnabled" value="true"/>

  • 二级缓存仅在同一个SqlSessionFactory创建的SqlSession中有效

  • 开启二级缓存生效的四个条件
    (1)在核心配置文件中,设置全局配置属性cacheEnabled="true",默认为true,不需要设置

    (2)在映射文件中设置标签

    (3)只有当一个SqlSession提交或关闭时,才会将该SqlSession的一级缓存存储到二级缓存当中

    (4)查询的数据所转换的实体类类型必须实现序列化的接口

注意:两次查询之间执行了任意的增删改,会使一级和二级缓存同时失效

二级缓存的相关配置
  • (1)在mapper配置文件中添加的cache标签可以设置一些属性:

  • (2)eviction属性:缓存回收策略

    • LRU(Least Recently Used) – 最近最少使用的:移除最长时间不被使用的对象。
    • FIFO(First in First out) – 先进先出:按对象进入缓存的顺序来移除它们。
    • SOFT – 软引用:移除基于垃圾回收器状态和软引用规则的对象。
    • WEAK – 弱引用:更积极地移除基于垃圾收集器状态和弱引用规则的对象。默认的是 LRU。
  • (3)flushInterval属性:刷新间隔,单位毫秒默认情况是不设置,也就是没有刷新间隔,缓存仅仅调用语句时刷新

  • (4)size属性:引用数目,正整数代表缓存最多可以存储多少个对象,太大容易导致内存溢出

  • (5)readOnly属性:只读,true/false

    • true:只读缓存;会给所有调用者返回缓存对象的相同实例。因此这些对象不能被修改。这提供了很重要的性能优势。
    • false:读写缓存;会返回缓存对象的拷贝(通过序列化)。这会慢一些,但是安全,因此默认是false。
    • 只读缓存,相当于把缓存中的引用直接交给了方法调用者。 读写缓存相当于每次都会拷贝一个对象交给调用者。

第三方二级缓存EHCache

引入依赖
<!-- Mybatis EHCache整合包 -->
<dependency>
<groupId>org.mybatis.caches</groupId>
<artifactId>mybatis-ehcache</artifactId>
<version>1.2.1</version>
</dependency>

<!-- slf4j日志门面的一个具体实现 -->
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>1.2.3</version>
</dependency>
各jar包的功能
  • (1)mybatis-ehcache Mybatis和EHCache的整合包

  • (2)ehcache EHCache核心包

  • (3)slf4j-api SLF4J日志门面包

  • (4)logback-classic 支持SLF4J门面接口的一个具体实现

在根路径下创建ehcache.xml
<?xml version="1.0" encoding="utf-8" ?>
<ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="../config/ehcache.xsd">
<!-- 磁盘保存路径 -->
<diskStore path="D:\mybatis_test\ehcache"/>
<defaultCache
maxElementsInMemory="1000"
maxElementsOnDisk="10000000"
eternal="false"
overflowToDisk="true"
timeToIdleSeconds="120"
timeToLiveSeconds="120"
diskExpiryThreadIntervalSeconds="120"
memoryStoreEvictionPolicy="LRU">
</defaultCache>
</ehcache>

配置文件的参数作用
属性名 是否必须 作用
maxElementsInMemory 在内存中缓存的element的最大数目
maxElementsOnDisk 在磁盘上缓存的element的最大数目,若是0表示无穷大
eternal 设定缓存的elements是否永远不过期。 如果为true,则缓存的数据始终有效, 如果为false那么还要根据timeToIdleSeconds、timeToLiveSeconds判断
overflowToDisk 设定当内存缓存溢出的时候是否将过期的element缓存到磁盘上
timeToIdleSeconds 当缓存在EhCache中的数据前后两次访问的时间超过timeToIdleSeconds的属性取值时, 这些数据便会删除,默认值是0,也就是可闲置时间无穷大
timeToLiveSeconds 缓存element的有效生命期,默认是0.,也就是element存活时间无穷大
diskSpoolBufferSizeMB DiskStore(磁盘缓存)的缓存区大小。默认是30MB。每个Cache都应该有自己的一个缓冲区
diskPersistent 在VM重启的时候是否启用磁盘保存EhCache中的数据,默认是false。
diskExpiryThreadIntervalSeconds 磁盘缓存的清理线程运行间隔,默认是120秒。每隔120s, 相应的线程会进行一次EhCache中数据的清理工作
memoryStoreEvictionPolicy 当内存缓存达到最大,有新的element加入的时候, 移除缓存中element的策略。 默认是LRU(最近最少使用),可选的有LFU(最不常使用)和FIFO(先进先出)
修改二级缓存的类型
<cache type="org.mybatis.caches.ehcache.EhcacheCache"/>

创建logback.xml配置文件
<?xml version="1.0" encoding="UTF-8"?>
<configuration debug="true">
    <!-- 指定日志输出的位置 -->
    <appender name="STDOUT"
              class="ch.qos.logback.core.ConsoleAppender">
    <encoder>
    <!-- 日志输出的格式 -->
    <!-- 按照顺序分别是:时间、日志级别、线程名称、打印日志的类、日志主体内容、换行 -->
    <pattern>[%d{HH:mm:ss.SSS}] [%-5level] [%thread] [%logger]
    [%msg]%n</pattern>
    </encoder>
    </appender>
    <!-- 设置全局日志级别。日志级别按顺序分别是:DEBUG、INFO、WARN、ERROR -->
    <!-- 指定任何一个日志级别都只打印当前级别和后面级别的日志。 -->
    <root level="DEBUG">
        <!-- 指定打印日志的appender,这里通过“STDOUT”引用了前面配置的appender -->
        <appender-ref ref="STDOUT" />
    </root>
    <!-- 根据特殊需求指定局部日志级别 -->
    <logger name="com.codestarts.mapper" level="DEBUG"/>
</configuration>

缓存查询的先后顺序

  • (1)先查询二级缓存

  • (2)再查询一级缓存

  • (3)最后查询数据库

  • SqlSession关闭之后,一级缓存中的数据会写入二级缓存

MyBatis逆向工程

正向工程与逆向工程的区别

  • 正向工程:先创建实体类,再根据实体类生成数据库表

  • 逆向工程:先创建数据库表,再根据数据库表生成实体类,逆向生成的资源如下

    • Java实体类
    • Mapper接口
    • Mapper映射文件

引入jar包及Maven插件

    <!-- 依赖MyBatis核心包 -->
    <dependencies>
        <dependency>
            <groupId>org.mybatis</groupId>
            <artifactId>mybatis</artifactId>
            <version>3.5.7</version>
        </dependency>
    </dependencies>
    <!-- 控制Maven在构建过程中相关配置 -->
    <build>
        <!-- 构建过程中用到的插件 -->
        <plugins>
            <!-- 具体插件,逆向工程的操作是以构建过程中插件形式出现的 -->
            <plugin>
                <groupId>org.mybatis.generator</groupId>
                <artifactId>mybatis-generator-maven-plugin</artifactId>
                <version>1.3.0</version>
                <!-- 插件的依赖 -->
                <dependencies>
                    <!-- 逆向工程的核心依赖 -->
                    <dependency>
                        <groupId>org.mybatis.generator</groupId>
                        <artifactId>mybatis-generator-core</artifactId>
                        <version>1.3.2</version>
                    </dependency>
                    <!-- 数据库连接池 -->
                    <dependency>
                        <groupId>com.mchange</groupId>
                        <artifactId>c3p0</artifactId>
                        <version>0.9.2</version>
                    </dependency>
                    <!-- MySQL驱动 -->
                    <dependency>
                        <groupId>mysql</groupId>
                        <artifactId>mysql-connector-java</artifactId>
                        <version>8.0.28</version>
                    </dependency>
                </dependencies>
            </plugin>
        </plugins>
    </build>

添加generatorConfig.xml配置文件并运行

  • 添加了之后找到对应的插件运行即可
    image
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE generatorConfiguration
        PUBLIC "-//mybatis.org//DTD MyBatis Generator Configuration 1.0//EN"
        "http://mybatis.org/dtd/mybatis-generator-config_1_0.dtd">
<generatorConfiguration>
    <!--
targetRuntime: 执行生成的逆向工程的版本
MyBatis3Simple: 生成基本的CRUD(清新简洁版)
MyBatis3: 生成带条件的CRUD(奢华尊享版)
-->
    <context id="DB2Tables" targetRuntime="MyBatis3">
        <!-- 数据库的连接信息 -->
        <jdbcConnection driverClass="com.mysql.jdbc.Driver"
                        connectionURL="jdbc:mysql://localhost:3306/db_mybatis?useSSL=false&amp;useUnicode=true&amp;characterEncoding=utf8&amp;serverTimezone=Asia/Shanghai"
                        userId="root"
                        password="abc123">
        </jdbcConnection>
        <!-- javaBean的生成策略-->
        <javaModelGenerator targetPackage="com.codestats.mybatis.bean"
                            targetProject=".\src\main\java">
            <property name="enableSubPackages" value="true"/>
            <property name="trimStrings" value="true"/>
        </javaModelGenerator>
        <!-- SQL映射文件的生成策略 -->
        <sqlMapGenerator targetPackage="com.codestats.mybatis.mapper"
                         targetProject=".\src\main\resources">
            <property name="enableSubPackages" value="true"/>
        </sqlMapGenerator>
        <!-- Mapper接口的生成策略 -->
        <javaClientGenerator type="XMLMAPPER"
                             targetPackage="com.codestats.mybatis.mapper" targetProject=".\src\main\java">
            <property name="enableSubPackages" value="true"/>
        </javaClientGenerator>
        <!-- 逆向分析的表 -->
        <!-- tableName设置为*号,可以对应所有表,此时不写domainObjectName -->
        <!-- domainObjectName属性指定生成出来的实体类的类名 -->
        <table tableName="dept" domainObjectName="Dept"/>
        <table tableName="employee" domainObjectName="Employee"/>
    </context>
</generatorConfiguration>

MyBatis分页插件

在pom.xml文件中引入插件

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

在mybatis全局配置文件中配置

    <plugins>
        <!--设置分页插件-->
        <plugin interceptor="com.github.pagehelper.PageInterceptor"></plugin>
    </plugins>

PageInfo及参数解释

    // 开启分页
    PageHelper.startPage(2, 3);
    List<Employee> employees = mapper.selectList();
    // 获取PageInfo
    PageInfo<Employee> pageInfo = new PageInfo<Employee>(employees);
    // 导航分页的页码数量
    PageInfo<Employee> pageInfo2 = new PageInfo<Employee>(employees, 3);
  • (1)pageNum:当前页的页码

  • (2)pageSize:每页显示的条数

  • (3)size:当前页显示的真实条数

  • (4)total:总记录数

  • (5)pages:总页数

  • (6)prePage:上一页的页码

  • (7)nextPage:下一页的页码

  • (8)isFirstPage/isLastPage:是否为第一页/最后一页

  • (9)hasPreviousPage/hasNextPage:是否存在上一页/下一页

  • (10)navigatePages:导航分页的页码数

  • (11)navigatepageNums:导航分页的页码,[1,2,3,4,5]

使用mybatis过程中出现的问题

在mapper.xml中,判断某个字段不是空字符串时,发现整数0会被当成空字符串..

判断的时候,要么先把int转换成string,要么不判断是否等于空字符串

Spring的学习

Spring的特性及五大功能模块

特性

  • (1)非侵入式:使用 Spring Framework 开发应用程序时,Spring 对应用程序本身的结构影响非常小。对领域模型可以做到零污染;对功能性组件也只需要使用几个简单的注解进行标记,完全不会破坏原有结构,反而能将组件结构进一步简化。这就使得基于 Spring Framework 开发应用程序时结构清晰、简洁优雅。

  • (2)控制反转:IOC——Inversion of Control,翻转资源获取方向。把自己创建资源、向环境索取资源变成环境将资源准备好,我们享受资源注入。

  • (3)面向切面编程:AOP——Aspect Oriented Programming,在不修改源代码的基础上增强代码功能。面向对象是纵向优化,优化的是从上往下执行的代码,而面向切面是横向优化,可以在不改变原有代码的基础上对原有的代码进行功能的扩展。

  • (4)容器:Spring IOC 是一个容器,因为它包含并且管理组件对象的生命周期。组件享受到了容器化的管理,替程序员屏蔽了组件创建过程中的大量细节,极大的降低了使用门槛,大幅度提高了开发效率。

  • (5)组件化:Spring 实现了使用简单的组件配置组合成一个复杂的应用。在 Spring 中可以使用 XML和 Java 注解组合这些对象。这使得我们可以基于一个个功能明确、边界清晰的组件有条不紊的搭建超大型复杂应用系统。

  • (6)声明式:很多以前需要编写代码才能实现的功能,现在只需要声明需求即可由框架代为实现。编程式指的是自己编写代码完成功能,例如以前的事务控制,都需要手动编写代码完成事务的开启、提交、回滚。而声明式则可以将这一工作交由框架来实现,换句话说,现在想要实现事务就只需要跟Spring声明一声即可

  • (7)一站式:在 IOC 和 AOP 的基础上可以整合各种企业应用的开源框架和优秀的第三方类库。而且Spring 旗下的项目已经覆盖了广泛领域,很多方面的功能性需求可以在 Spring Framework 的基础上全部使用 Spring 来实现。

五大功能模块

  • (1)Core Container 核心容器,在 Spring 环境下使用任何功能都必须基于 IOC 容器。

  • (2)AOP&Aspects 面向切面编程

  • (3)Testing 提供了对 junit 或 TestNG 测试框架的整合。

  • (4)Data Access/Integration 提供了对数据访问/集成的功能。DI依赖注入,可以理解为IOC思想的一种具体实现,通俗易懂的作用就是为Spring容器中的属性进行赋值

  • (5)Spring MVC 提供了面向Web应用程序的集成功能。

SpringIOC容器

Spring对于IOC容器的实现

  • (1)BeanFactory :这是 IOC 容器的基本实现,是 Spring 内部使用的接口。面向 Spring 本身,不提供给开发人员使用。

  • (2)ApplicationContext:BeanFactory 的子接口,提供了更多高级特性。面向 Spring 的使用者,几乎所有场合都使用ApplicationContext 而不是底层的 BeanFactory。主要实现类如下:
    image

类型名 简介
ClassPathXmlApplicationContext 通过读取类路径下的 XML 格式的配置文件创建 IOC 容器对象
FileSystemXmlApplicationContext 通过文件系统路径读取 XML 格式的配置文件创建 IOC 容器对象(不常用)
ConfigurableApplicationContext ApplicationContext 的子接口,包含一些扩展方法refresh()close() ,让 ApplicationContext 具有启动、关闭和刷新上下文的能力。
WebApplicationContext 专门为 Web 应用准备,基于 Web 环境创建 IOC 容器对象,并将对象引入存入 ServletContext 域中。

基于XML管理Bean

获取Bean的三种方式

  • 注意:被Spring IOC容器所管理的bean,可以通过其bena对象的父类或接口类型来获取

  • applicationContext.xml文件

<bean id="userController" class="com.codestars.controller.UserController"/>
  • 测试类
    // 通过bean的唯一标志id来获取
    UserController userController1 = (UserController) ac.getBean("userController");
    System.out.println("userController1 = " + userController1);

    // 通过类型来获取(最常用)
    UserController userController2 = ac.getBean(UserController.class);
    System.out.println("userController2 = " + userController2);

    // 通过id + 类型来获取
    UserController userController3 = ac.getBean("userController", UserController.class);
    System.out.println("userController3 = " + userController3);

DI依赖注入的2种方式

  • set/get方式
<bean id="user1" class="com.codestars.pojo.User">
       <property name="name" value="张三"></property>
       <property name="age" value="15"></property>
</bean>
  • 构造器方式
<bean id="user2" class="com.codestars.pojo.User">
       <constructor-arg name="name" value="李四"></constructor-arg>
       <constructor-arg name="age" value="18"></constructor-arg>
</bean>
字面量赋值

  基本数据类型包装数据类型String类型这种看到什么就代表什么的值成为字面量。进行以来依赖注入时,字面量使用value赋值。

<bean id="user1" class="com.codestars.pojo.User">
       <property name="name" value="张三"></property>
</bean>
为属性赋值为null
<bean id="user1" class="com.codestars.pojo.User">
       <property name="name">
              <null/>
       </property>
</bean>
当遇到xml实体时的2种处理方案
  • 使用xml实体对应的实体符号,例如大于号变成&gt,小于号变成&lt等

  • 使用CDATA节

<bean id="user1" class="com.codestars.pojo.User">
       <property name="name">
              <value><![CDATA[><><><><><><><]]></value>
       </property>
</bean>
为类类型的属性赋值
引用外部已声明的bean
<bean id="user1" class="com.codestars.pojo.User">
       <property name="cat" ref="myCat"></property>
</bean>

<bean id="myCat" class="com.codestars.pojo.Cat"></bean>
使用内部bean(内部bean无法在外部通过IOC容器获取)
<bean id="user1" class="com.codestars.pojo.User">
       <property name="cat">
              <bean class="com.codestars.pojo.Cat"></bean>
       </property>
</bean>
为数组类型赋值
<bean id="user1" class="com.codestars.pojo.User">
       <property name="catArr">
              <array>
                     <!-- 注意:字面量使用value标签,类变量可以考虑内部bean,也可以考虑外部引用 -->
                     <!-- 赋值3个内部bean -->
                     <bean class="com.codestars.pojo.Cat"></bean>
                     <bean class="com.codestars.pojo.Cat"></bean>
                     <bean class="com.codestars.pojo.Cat"></bean>

                     <!-- 外部引用3个bean -->
                     <ref bean="cat1"></ref>
                     <ref bean="cat2"></ref>
                     <ref bean="cat3"></ref>
              </array>
       </property>
</bean>

<bean id="cat1" class="com.codestars.pojo.Cat"></bean>
<bean id="cat2" class="com.codestars.pojo.Cat"></bean>
<bean id="cat3" class="com.codestars.pojo.Cat"></bean>
为List集合属性赋值
<bean id="user1" class="com.codestars.pojo.User">
       <property name="integerList">
              <list>
                     <!-- 由于是存储字面量的List,所以使用value标签 -->
                     <value>12</value>
                     <value>33</value>
                     <value>22</value>
                     <value>141</value>
              </list>
       </property>
</bean>
为Map集合属性赋值
<bean id="user1" class="com.codestars.pojo.User">
       <property name="map">
              <!-- 这个map是 <String, Cat> 类型的 -->
              <map>
                     <entry key="catOne" value-ref="cat1"></entry>
                     <entry key="catTwo" value-ref="cat2"></entry>
                     <entry key="catThree" value-ref="cat3"></entry>
              </map>
       </property>
</bean>

<bean id="cat1" class="com.codestars.pojo.Cat"></bean>
<bean id="cat2" class="com.codestars.pojo.Cat"></bean>
<bean id="cat3" class="com.codestars.pojo.Cat"></bean>
创建集合、数组、Map类型的bean并引用
<bean id="user1" class="com.codestars.pojo.User">
       <property name="map" ref="catMap"></property>
</bean>
       
<bean id="cat1" class="com.codestars.pojo.Cat"></bean>
<util:map id="catMap">
       <entry key="catOne">
              <!-- 使用内部bean -->
              <bean class="com.codestars.pojo.Cat"></bean>
       </entry>
       <entry key="catTwo" value-ref="cat1"></entry>
</util:map>
命名空间p(了解)
<bean id="user1" class="com.codestars.pojo.User" p:age="15" p:name="张三" p:integerList-ref="integerList"></bean>
<util:list id="integerList">
       <value>13</value>
       <value>11</value>
       <value>44</value>
       <value>66</value>
</util:list>

引入外部配置文件

<!-- 引入外部properties配置文件,引入后可通过${key}的方式获取 -->
<context:property-placeholder  location="jdbc.properties"></context:property-placeholder>

<bean id="user1" class="com.codestars.pojo.User">
       <property name="strArr">
              <array>
                     <value>${jdbc.driver}</value>
                     <value>${jdbc.url}</value>
                     <value>${jdbc.username}</value>
                     <value>${jdbc.password}</value>
              </array>
       </property>
</bean>

bean的作用域

  • 通过修改bean标签的scope属性调整

  • 默认为singleton 单例模式,也可以修改为prototype多例模式

  • 在Web容器当中,还会有另外2个作用域

    • request 一个请求内有效
    • session 一个会话内有效

bean的生命周期

  • (1)bean对象创建(调用无参构造器)、实例化

  • (2)给bean对象设置属性、依赖注入

  • (3)bean对象初始化之前操作(由bean的后置处理器负责)

  • (4)bean对象初始化(需在配置bean时指定初始化方法)、初始化

  • (5)bean对象初始化之后操作(由bean的后置处理器负责)

  • (6)bean对象就绪可以使用、使用状态

  • (7)bean对象销毁(需在配置bean时指定销毁方法)、只有当关闭IOC容器时才会销毁

  • (8)IOC容器关闭、使用ConfigurableApplicationContext调用close()方法可以关闭IOC容器

后置处理器BeanPostProcessor
  • 可以在对象进行初始化之前以及初始化之后做一些事情(对所有Spring容器中的bean都有效

  • 创建一个类实现该接口

public class PostProcessorTest  implements BeanPostProcessor {
    @Override
    public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
        System.out.println("对象实例化之前");
        return bean;
    }

    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
        System.out.println("对象实例化后");
        return bean;
    }
}
  • 在xml文件中配置
<bean class="com.codestars.PostProcessorTest"></bean>

FactoryBean(以后spring整合mybatis时,Mybatis就提供了一个FactoryBean)

  • 获取该FactoryBean 就相当于获取一个其getObject()方法返回值类型的实例

  • 创建一个类,实现该接口

public class UserFactory implements FactoryBean<User> {
    @Override
    public User getObject() throws Exception {
        return new User();
    }

    @Override
    public Class<?> getObjectType() {
        return User.class;
    }

    /**
     * 是否为单例,指的就是获取的实例是否为单例。
     * @return
     */
    @Override
    public boolean isSingleton() {
        return true;
    }
}
  • 在配置文件中进行配置
<!-- 通过getBean("user1") 得到的Bean对象类型就是User类型 -->
<bean id="user1" class="com.codestars.factory.UserFactory"></bean>

自动装配

基于XML的自动装配byType 和 byName(不常用)
  • byType : 根据类型自动装配。byName:根据bean的唯一标志与属性名称自动装配

  • 为一个bean配置了自动装配后,则会按照自动装配的规则将该bean中的所有非字面量

  • xml配置文件

<bean id="user1" class="com.codestars.pojo.User" autowire="byName"></bean>
<bean id="user2" class="com.codestars.pojo.User" autowire="byType"></bean>

<bean id="isNullCat" class="com.codestars.pojo.Cat"></bean>

基于注解管理Bean

基于注解管理bean的4个注解以及配置包扫描

  • 用于标识需要被SpringIOC容器所管理的注解,这四个注解的作用都是一样的,只是不同的名称有利于我们程序员开发。
@Controller 标识为一个控制层的组件
@Service	标识为一个业务层的组件
@Repository 标识为一个持久层的组件
@Component  标识为一个组件(当如上3个都不是时)
  • 通过注解扫描的方式,bean的id默认为类名的小驼峰
扫描组件的3种方式
  • 最基础的注解扫描
    <!-- 这里表示扫描com.codestars这个包及其自包下的所有类 -->
    <context:component-scan base-package="com.codestars" />
  • 排除不需要扫描的注解
    <!-- 这里表示扫描com.codestars这个包及其自包下的所有类 -->
    <context:component-scan base-package="com.codestars">
        <!-- type:annotation 按照注解排除不需要加载到IOC容器的类 -->
        <context:exclude-filter type="annotation" expression="org.springframework.stereotype.Controller" />
        <!-- type:assignable 按照类型排除 -->
        <context:exclude-filter type="assignable" expression="com.codestars.controller.UserController" />
    </context:component-scan>
  • 添加需要扫描的注解,需要配合use-default-filters="false"
    <!-- 这里表示扫描com.codestars这个包及其自包下的所有类 -->
    <context:component-scan base-package="com.codestars" use-default-filters="false">
        <!-- type:annotation 按照注解添加需要加载到IOC容器的类 -->
        <context:include-filter type="annotation"  expression="org.springframework.stereotype.Controller" />
        <!-- type:assignable 按照类型扫描 -->
        <context:include-filter type="assignable" expression="com.codestars.service.impl.UserServiceImpl" />
    </context:component-scan>

基于注解的自动装配

  • 被Spring管理的组件中使用@Autowired作用于成员变量、set方法、构造器上均可以完成自动注入

  • @Autowired的注入原理:先使用byType的方式,若容器中包含有多个同类型的bean,则会使用byName的方式获取,若仍然没有获取到,则抛出异常。

  • 有一个注解:@Qualifier可以标识byType获取组件时使用的beanId

SpringAOP

什么是AOP,OOP

  • AOP : Aspect Oriented Programming 面向切面编程,采用的是横向拓展机制,可以在不影响原有功能实现的基础上,完成非入侵式的功能扩展

  • OOP:Object Oriented Programming 面向对象编程,采用的是纵向继承极致,当我们需要进行代码的重用时,需要将被重用的代码纵向抽取,无法解决横向扩展的问题。例如一个计算器功能,我们想要在计算的前后添加上日志功能,通过面向对象的方法就无法解决

静态代理、动态代理的区别

  • 用生活中的例子来理解代理模式
      例如我们的一个普通的饮水机,我们想要让饮水机具备水过滤的功能,因此我们将饮水机进行了改装,在不影响原有功能的情况下,添加了过滤功能。之后我们使用的时候就只能使用带过滤功能的饮水机。

使用计算器功能完成一个静态代理

Calculate 计算器接口
public interface Calculate {
    Integer add(int a, int b);
    Integer sub(int a, int b);
    Integer mul(int a, int b);
    Integer div(int a, int b);
}
CalculateImpl 计算器实现类
public class CalculateImpl implements Calculate {
    @Override
    public Integer add(int a, int b) {
        return a + b;
    }

    @Override
    public Integer sub(int a, int b) {
        return a - b;
    }

    @Override
    public Integer mul(int a, int b) {
        return a * b;
    }

    @Override
    public Integer div(int a, int b) {
        return a / b;
    }
}
思考一下,这个时候如果我们想要增加一些非核心代码应该怎么办?
  • 难道在计算器实现类中的每个方法的前后都添加这些非核心代码吗?

  • 这么做的弊端有如下几个:
    (1)会影响程序员开发核心代码,应该在开发核心代码时,还要考虑非核心代码,比如日志应该如何添加等问题。
    (2)会造成大量的重复代码,形成代码的冗余,不便于管理。

编写一个静态代理类来完成功能的扩展
public class CalculateImplProxy implements Calculate{
    private CalculateImpl calculateImpl = new CalculateImpl();

    @Override
    public Integer add(int a, int b) {
        System.out.println("日志功能,当前执行的方法是:add" + "参数为:" + a + " ," + b) ;

        Integer result = calculateImpl.add(a, b);

        System.out.println("日志功能,当前执行的方法是:add,执行结果为: " + result);
        return result;
    }

    @Override
    public Integer sub(int a, int b) {
        System.out.println("日志功能,当前执行的方法是:sub" + "参数为:" + a + " ," + b) ;

        Integer result = calculateImpl.sub(a, b);

        System.out.println("日志功能,当前执行的方法是:sub,执行结果为: " + result);
        return result;
    }

    @Override
    public Integer mul(int a, int b) {
        System.out.println("日志功能,当前执行的方法是:mul" + "参数为:" + a + " ," + b) ;

        Integer result = calculateImpl.mul(a, b);

        System.out.println("日志功能,当前执行的方法是:mul,执行结果为: " + result);
        return result;
    }

    @Override
    public Integer div(int a, int b) {
        System.out.println("日志功能,当前执行的方法是:div" + "参数为:" + a + " ," + b) ;

        Integer result = calculateImpl.div(a, b);

        System.out.println("日志功能,当前执行的方法是:div,执行结果为: " + result);
        return result;
    }
}
因此再使用的时候必须使用代理对象
public class Test {
    public static void main(String[] args) {
        Calculate calculate = new CalculateImplProxy();
        calculate.add(5, 2);
        calculate.sub(3, 2);
        calculate.mul(2, 2);
        calculate.div(5, 2);
    }
}
静态代理的弊端
  • 静态代理时,一个代理类只能对应一个目标类,因此一旦有多个类都需要被代理时,那么就得编写多个代理类

  • 并且如果有多个需要被代理的类,想要扩展的都是同样的功能,那不就依然造成代码的冗余了吗?

在如上计算器的基础上使用JDK动态代理完成计算器代理功能

public class ProxyFactory {

    public static Object getProxyObject(Object target) {


        /**
         * ClassLoader loader,  由于代理类需要被动态生成,因此需要一个类加载器
         * Class<?>[] interfaces, 想想我们刚刚写的静态代理,代理类需要跟目标类实现一样的接口
         * InvocationHandler h , 进行对目标对象功能的增强处理
         */
        ClassLoader classLoader = ProxyFactory.class.getClassLoader();
        Class<?>[] interfaces = target.getClass().getInterfaces();
        InvocationHandler invocationHandler = new InvocationHandler() {
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                try {
                    // 前置通知
                    System.out.println("增强方法:前置通知,运行的方法为:" + method.getName() + " 参数列表为:" + Arrays.toString(args));

                    // 代理类,执行的依然是目标类的方法。为什么呢?代理类就是为了扩展目标类的方法,实际上还是使用的目标类的方法
                    Object resultObj = method.invoke(target, args);

                    // 方法有返回值的通知
                    System.out.println("方法正常运行完毕,运行的结果为:" + resultObj);

                    // 结果一定要返回去,为什么呢? 既然是扩展原有的目标类的方法,那么结果肯定不能变。
                    return resultObj;
                } catch (Exception e) {
                    // 异常通知
                    System.out.println("增强方法:异常通知");
                } finally {
                    // 后置通知
                    System.out.println("增强方法:后置通知");
                }
                return null;
            }
        };
        return Proxy.newProxyInstance(classLoader, interfaces, invocationHandler);
    }
}

AOP中的几个概念(7个,重要)

  • 横切关注点(需要抽取的非核心代码,理解为刚刚计算器功能中的日志代码)

  • 切面(将横切关注点中的代码进行抽取,抽取后放到一个类中,该类就是一个切面类)

  • 通知(切面类中的一个个存放横切关注点代码的方法

  • 连接点(当前的通知需要连接到哪个类上的哪个方法?需要连接到的地方就称之为连接点)

  • 切入点(其实就是连接点的具体表现形式,通过切入点表达式来表示需要连接的点)

  • 代理(代理对象)

  • 目标(需要扩展功能的被代理对象)

SpringAOP使用的步骤

将目标类和切面类都交由SpringIOC容器管理

  • 可以通过注解、或者XML的方式交由IOC容器管理

  • 一个类想要成为切面类,可以通过@Aspect注解标识为一个切面类

将横切关注点的代码放在切面类中的通知方法中

// 定义一个全局切入点,大家可以一起使用
    @Pointcut("execution(* com.codestars.service.impl.*.*(..))")
    public void pointCut(){}

// 定义不同的通知
@Before("pointCut()")
@After("pointCut()")
@AfterReturning(value = "pointCut()",returning = "result")
@AfterThrowing(value = "pointCut()",throwing = "ex")

演示基于注解的AOP

添加SpringIOC、SpringAOP、spring-aspects的依赖
    <dependencies>
        <!-- https://mvnrepository.com/artifact/org.springframework/spring-context -->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>5.3.18</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-aop</artifactId>
            <version>5.3.18</version>
        </dependency>

        <!-- spring-aspects会帮我们传递过来aspectjweaver -->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-aspects</artifactId>
            <version>5.3.1</version>
        </dependency>
        
        <!-- https://mvnrepository.com/artifact/junit/junit -->
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.13.2</version>
            <scope>test</scope>
        </dependency>
    </dependencies>
在resources路径下添加spring配置文件
    <!-- 这里表示扫描com.codestars这个包及其自包下的所有类 -->
    <context:component-scan base-package="com.codestars"></context:component-scan>

    <!-- 开启注解功能的AOP -->
    <aop:aspectj-autoproxy></aop:aspectj-autoproxy>
创建一个切面类(除了切入点表达式,还能使用@annotation)
# 用@annotation,MyLog是一个注解,这样会从spring容器中拦截@MyLog注解
@AfterReturning(value = "@annotation(log)",returning = "res")
public void afterReturningAdvice(JoinPoint point, MyLog log, Object res){

@Component
@Aspect
public class LoggerAspect {

    /**
     * 定义全局切入点,用于找到连接点
     * 完整格式为: public int com.codestars.service.impl.UserServiceImpl.save(这里可以写参数列表,写类型即可);
     * public int  可以简化为 *
     * 包名         可以简化为 *
     * 方法名        可以简化为 *
     * 参数列表      可以简化为 ..
     */
    @Pointcut("execution(* com.codestars.service.impl.*.*(..))")
    public void pointCut(){}

    /**
     * 前置通知,方法执行前执行
     * @param joinPoint 连接点信息
     */
    @Before("pointCut()")
    public void before(JoinPoint joinPoint) {
        Signature signature = joinPoint.getSignature();
        System.out.println("前置日志通知,方法执行前调用,当前执行的方法为:" + signature.getName());
    }

    /**
     * 后置通知,在方法完毕后执行,相当于finally中的代码
     * @param joinPoint 连接点信息
     */
    @After("pointCut()")
    public void after(JoinPoint joinPoint) {
        Signature signature = joinPoint.getSignature();
        System.out.println("后置通知,finally中执行的代码,当前执行的方法为:" + signature.getName());
    }

    /**
     * 方法正常执行并为抛出异常时执行,可以获取到方法的返回值
     * @param joinPoint
     * @param result
     */
    @AfterReturning(value = "pointCut()",returning = "result")
    public void afterReturning(JoinPoint joinPoint, Object result) {
        Signature signature = joinPoint.getSignature();
        System.out.println("方法执行未抛出异常,执行的返回结果为:" + result);
    }

    /**
     * 异常通知:方法抛出异常时执行
     * @param joinPoint
     * @param ex
     */
    @AfterThrowing(value = "pointCut()",throwing = "ex")
    public void AfterThrowing(JoinPoint joinPoint, Throwable ex) {
        System.out.println("异常通知,当前的异常为: " + ex);
    }

    @Around(value = "pointCut()")
    public Object aRound(ProceedingJoinPoint proceedingJoinPoint) {
        Object resultObj = null;
        try {
            // 环绕:前置通知
            System.out.println("环绕:前置通知");

            // 方法执行
            resultObj = proceedingJoinPoint.proceed();

            // 环绕:后置通知
            System.out.println("环绕:后置通知");
        } catch (Throwable e) {
            e.printStackTrace();
            System.out.println("环绕:异常通知,当前异常为:" + e);
        }  finally {
            System.out.println("环绕:后置通知,方法执行完毕");
        }
        // 一定要将目标对象执行方法的结果返回
        return resultObj;
    }
}
多个切面类的优先级
  • 默认都为Integer的最大值

  • 通过修改order属性(可以在切面类上使用@Order注解修改)

测试
    ApplicationContext ioc = new ClassPathXmlApplicationContext("spring-annoation.xml");
    UserService userService = ioc.getBean(UserService.class);
    userService.save();

基于XML的AOP配置(xml文件)

    <!-- 这里表示扫描com.codestars这个包及其自包下的所有类 -->
    <context:component-scan base-package="com.codestars"></context:component-scan>

    <aop:config>
        <!-- 定义全局切入点表达式 -->
        <aop:pointcut id="pointCut" expression="execution(* com.codestars.service.impl.*.*(..))"/>
        
        <!-- 配置一个切面类 -->
        <aop:aspect ref="loggerAspect">
            <!-- 指定各个通知的方法名 -->
            <aop:before method="before" pointcut-ref="pointCut"/>
            <aop:after method="after" pointcut-ref="pointCut"/>
            <aop:after-returning method="afterReturning" returning="result" pointcut-ref="pointCut"/>
            <aop:after-throwing method="afterReturning"  throwing="ex" pointcut-ref="pointCut"/>
            <aop:around method="aRound" pointcut-ref="pointCut"/>
        </aop:aspect>
    </aop:config>

基于注解的AOP完成声明式事物处理

引入IOC、AOP、ORM、TX、数据源等依赖

    <dependencies>
        <!-- 基于Maven依赖传递性,导入spring-context依赖即可导入当前所需所有jar包 -->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>5.3.1</version>
        </dependency>

        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-aop</artifactId>
            <version>5.3.18</version>
        </dependency>
        
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-aspects</artifactId>
            <version>5.3.1</version>
        </dependency>
        <!-- Spring 持久化层支持jar包 -->
        <!-- Spring 在执行持久化层操作、与持久化层技术进行整合过程中,需要使用orm、jdbc、tx三个
        jar包 -->
        <!-- 导入 orm 包就可以通过 Maven 的依赖传递性把其他两个也导入 -->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-orm</artifactId>
            <version>5.3.1</version>
        </dependency>
        <!-- Spring 测试相关 -->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-test</artifactId>
            <version>5.3.1</version>
        </dependency>
        <!-- junit测试 -->
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.12</version>
            <scope>test</scope>
        </dependency>
        <!-- MySQL驱动 -->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>8.0.16</version>
        </dependency>
        <!-- 数据源 -->
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid</artifactId>
            <version>1.0.31</version>
        </dependency>
    </dependencies>

在Spring的xml文件中进行配置

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

    <!-- 引入外部配置文件 -->
    <context:property-placeholder location="jdbc.properties"/>
    <!-- 扫描包 -->
    <context:component-scan base-package="com.codestars"/>

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

    <!-- spring提供的封装jdbc的类 -->
    <bean class="org.springframework.jdbc.core.JdbcTemplate">
        <property name="dataSource" ref="dataSource"></property>
    </bean>

    <!-- 这是一个切面类,帮我们完成了事物管理功能 -->
    <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <property name="dataSource" ref="dataSource"></property>
    </bean>

    <!-- 开启该功能后,会通过扫描@Transactional 注解来将类中的所有方法或单独的方法作为事物切面类的连接点 -->
    <tx:annotation-driven transaction-manager="transactionManager"/>
</beans>

在需要进行事物管理的类或方法上加注解

  • 在类上添加@Transactional 注解:标识该类中所有的方法都为连接点,会对该类中所有方法进行事物管理

  • 在方法上添加@Transactional注解:标识该方法为连接点,将会进行事物管理

事物的传播行为

思考:业务层的一个方法调用业务层的另外一个方法。那么事物应该怎么处理呢?

  • REQUIRED 被调用者与调用者使用同一个事物(默认)

  • REQUIRES_NEW 被调用者会开启一个新的事物(常用)

Spring提供的事物管理常用的配置

<!-- read-only属性:设置只读属性 -->
true:表示只读,false表示可读可写

<!-- rollback-for属性:设置回滚的异常 -->
<!-- no-rollback-for属性:设置不回滚的异常 -->
<!-- isolation属性:设置事务的隔离级别 -->
<!-- timeout属性:设置事务的超时属性 -->
<!-- propagation属性:设置事务的传播行为 -->

使用XML方式完成事物的管理

    <!-- 这是一个切面类,帮我们完成了事物管理功能 -->
    <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <property name="dataSource" ref="dataSource"></property>
    </bean>

    <tx:advice id="txAdvice" transaction-manager="transactionManager">
        <tx:attributes>
            <!-- 指定连接点,为查询操作设置只读 -->
            <tx:method name="query*" read-only="true"/>
            <tx:method name="find*" read-only="true"/>
            <tx:method name="get*" read-only="true"/>

            <tx:method name="save*" read-only="false"/>
            <tx:method name="buy*" read-only="false"/>
            <tx:method name="update*" read-only="false"/>
            <tx:method name="delete*" read-only="false" />
        </tx:attributes>
    </tx:advice>

    <aop:config>
        <aop:pointcut id="pointCut" expression="execution(* com.codestars.service.impl.*.*(..))"/>
        <aop:advisor advice-ref="txAdvice"  pointcut-ref="pointCut"></aop:advisor>
    </aop:config>

SpringMVC的学习

SpringMVC是什么?

SpringMVC是Spring为我们提供的表现层的一站式解决方案。

开发WEB端的一些常见问题

服务器端的请求转发/重定向与浏览器解析路径: /的区别

  • 在服务器端解析/时,路径为:协议+端口号+上下文路径

  • 浏览器解析/时,路径为:协议+端口号

mvc:annotation-driven注解驱动的作用

  • 在使用view-controller标签时

  • 在使用 @RequestBody@RequestResponse

  • 在设置静态静态资源时需要配合 <mvc:default-servlet-handler/>使用

RequestMapping注解

value属性

  • 表示需要跳转到逻辑视图

method属性

  • 表示能够接收到请求方式,RequestMethod枚举类中可以看到能够设置的值

params属性(了解)

  • "param":要求请求映射所匹配的请求必须携带param请求参数

  • "!param":要求请求映射所匹配的请求必须不能携带param请求参数

  • "param=value":要求请求映射所匹配的请求必须携带param请求参数且param=value

  • "param!=value":要求请求映射所匹配的请求必须携带param请求参数但是param!=value

headers属性(了解)

  • "header":要求请求映射所匹配的请求必须携带header请求头信息

  • "!header":要求请求映射所匹配的请求必须不能携带header请求头信息

  • "header=value":要求请求映射所匹配的请求必须携带header请求头信息且header=value

  • "header!=value":要求请求映射所匹配的请求必须携带header请求头信息且header!=value

  • 提示:若当前请求满足@RequestMapping注解的value和method属性,但是不满足headers属性,此时页面显示404错误,即资源未找到

SpringMVC支持ant风格的路径(了解)

  • ?:表示任意的单个字符

  • *:表示任意的0个或多个字符

  • **:表示任意层数的任意目录

  • 注意:在使用**时,只能使用/**/xxx的方式

支持路径中的占位符(重点),RestFul风格接口

    @RequestMapping("/testRest/{id}/{username}")
    public String testRest(@PathVariable("id") String id, @PathVariable("username")
            String username) {
        System.out.println("id:" + id + ",username:" + username);
        return "success";
    }

获取请求参数

通过原生ServletAPI获取

    @RequestMapping("/testParam")
    public String testParam(HttpServletRequest request) {
        String username = request.getParameter("username");
        String password = request.getParameter("password");
        System.out.println("username:" + username + ",password:" + password);
        return "success";
    }

通过控制器方法的形参获取请求参数

    @RequestMapping("/testParam")
    public String testParam(String username, String password) {
        System.out.println("username:" + username + ",password:" + password);
        return "success";
    }

@RequestParam注解

  • @RequestParam是将请求参数和控制器方法的形参创建映射关系

  • @RequestParam注解一共有三个属性:

    • value:指定为形参赋值的请求参数的参数名

    • required:设置是否必须传输此请求参数,默认值为true

    • 若设置为true时,则当前请求必须传输value所指定的请求参数,若没有传输该请求参数,且没有设置defaultValue属性,则页面报错400:Required String parameter 'xxx' is not present;若设置为false,则当前请求不是必须传输value所指定的请求参数,若没有传输,则注解所标识的形参的值为null

    • defaultValue:不管required属性值为true或false,当value所指定的请求参数没有传输或传输的值为""时,则使用默认值为形参赋值

@RequestHeader注解(了解)

  • @RequestHeader是将请求头信息和控制器方法的形参创建映射关系

  • @RequestHeader注解一共有三个属性:value、required、defaultValue,用法同@RequestParam

@CookieValue(了解)

  • @CookieValue是将浏览器发送的Cookie信息和控制器方法的形参创建映射关系

  • 用法同@RequestParam

通过POJO获取请求参数

  • 在控制器的形参列表写上一个POJO类型时,SpringMVC会自动通过request.getParameter()获取参数后封装到该POJO类中
    @RequestMapping("/testPojoParam")
    public String testParam(User user) {
        System.out.println(user);
        return "success";
    }

解决获取参数的乱码问题

  • 一旦使用了reqeust.getParameter()获取了参数之后再设置编码就没有效果了。而SpringMVC帮助我们封装了Servlet,我们只需要编写控制器,而进入到控制器方法之前,SpringMVC已经帮助我们获取过参数了,此时再设置编码就没有效果了。

  • 因此我们需要在SpringMVC的Servlet执行之前进行编码的过滤,这个时候我们可以考虑使用过滤器,虽然我们自己写一个过滤器也可以,但是SpringMVC已经为我们提供了编码过滤器了

在web.xml中配置SpringMVC提供的编码过滤器

    <filter>
        <filter-name>CharacterEncodingFilter</filter-name>
        <filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
        <!-- 如果只配置encoding,则只会设置request.setCharacterEncoding的编码 ,但是不会设置response的响应编码 -->
        <init-param>
            <param-name>encoding</param-name>
            <param-value>UTF-8</param-value>
        </init-param>
        <!-- 设置该参数后,还会设置response的响应编码 -->
        <init-param>
            <param-name>forceEncoding</param-name>
            <param-value>true</param-value>
        </init-param>
    </filter>
    <filter-mapping>
        <filter-name>CharacterEncodingFilter</filter-name>
        <url-pattern>/*</url-pattern>
    </filter-mapping>
过滤器的过滤源码
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
        String encoding = this.getEncoding();
        if (encoding != null) {
            if (this.isForceRequestEncoding() || request.getCharacterEncoding() == null) {
                request.setCharacterEncoding(encoding);
            }

            if (this.isForceResponseEncoding()) {
                response.setCharacterEncoding(encoding);
            }
        }

        filterChain.doFilter(request, response);
    }

域对象共享数据

使用ServletAPI向request域对象共享数据

    @RequestMapping("/index")
    public String index(HttpServletRequest request) {
        request.setAttribute("username","张三");
        return "index";
    }

使用ModelAndView向request域对象共享数据

    @RequestMapping("/modelAndView")
    public ModelAndView testModelAndView() {
        ModelAndView mv = new ModelAndView();
        mv.addObject("username","李四");
        mv.setViewName("index");
        return mv;
    }

使用Model向request域对象共享数据

    @RequestMapping("/model")
    public String testModelAndView(Model model) {
        model.addAttribute("username","王五");
        return "index";
    }

使用map向request域对象共享数据

    @RequestMapping("/map")
    public String testModelAndView(Map<String, Object> map) {
        map.put("username","赵流");
        return "index";
    }

使用ModelMap向request域对象共享数据

    @RequestMapping("/modelMap")
    public String testModelAndView(ModelMap modelMap) {
        // 两种方式都可以
        modelMap.put("username","田七");
        modelMap.addAttribute("username2","田七2");
        return "index";
    }

Model、ModelMap、Map的关系

  • SpringMVC在进行视图解析时,最终都会将这些类转换为ModelAndView

  • 在DispatcherServlet的第1067行可以看到mv = ha.handle(processedRequest, response, mappedHandler.getHandler());,最终这些类型都会被转换为ModelAndView

向session域共享数据

    @RequestMapping("/session")
    public String session(HttpSession session) {
        session.setAttribute("username","王八");
        return "index";
    }

向application域共享数据


    @RequestMapping("/application")
    public String application(HttpSession session) {
        ServletContext servletContext = session.getServletContext();
        servletContext.setAttribute("username","九九");
        return "index";
    }

SpringMVC的视图

ThymeleafView

  • 当控制器中所设置的视图名称没有任何前缀时,此时的视图名称会被SpringMVC中所配置的视图解析器解析,如果视图解析器配置了Thymeleaf解析器,那么将会使用ThymeleafView来解析。

  • 可以通过debug DispatcherServlet的render方法查看,1377行

InternalResourceView转发视图

  • 当控制器中所设置的视图名称前缀为: forward:时,将会使用InternalResourceView视图解析器来解析,但是如果使用了该视图解析器跳转视图,会导致页面无法被Thymeleaf模板引擎所渲染。

RedirectView 重定向视图

  • 当控制器中所设置的视图名称前缀为: redirect:时,将会使用RedirectView视图解析器所解析,进行页面的重定向。

视图控制器view-controller

  • 当有一些控制器,我们仅仅是用来进行页面跳转时,就可以使用view-controller

  • 当SpringMVC中设置任何一个view-controller时,其他控制器中的请求映射将全部失效,此时需要在SpringMVC的核心配置文件中设置开启mvc注解驱动的标签

  • 在springmvc.xml文件中进行配置

    • 添加注解驱动
    • 添加一个view-contoller
    <!--当SpringMVC中设置任何一个view-controller时,其他控制器中的请求映射将全部失效
    此时需要在SpringMVC的核心配置文件中设置开启mvc注解驱动的标签-->
    <mvc:annotation-driven></mvc:annotation-driven>
    <mvc:view-controller path="/login"  view-name="login"/>

RestFul风格

操作 传统方式 RESTFul风格
查询单个用户 getUserById?id=1 user/1 --> get请求方式
查询所有用户 getUserList user --> get请求方式
新增用户 saveUser user --> post请求方式
删除用户 delUser?id=1 user/1 --> delete请求方式
更新用户 updateUser user --> put请求方式
更新用户界面 updateUserPage user/1 --> put请求方式

HiddenHttpMethodFilter过滤器

  • 当我们的表单想要提交一个put或者delete请求时,可以使用该过滤器

  • 使用方式:
    (1)设置form表单的提交方式为post
    (2)在form表单中添加一个隐藏的input,设置name为_method,其value值为需要请求的方式

  • 配置HiddenHttpMethodFilter过滤器

    <filter>
        <filter-name>HiddenHttpMethodFilter</filter-name>
        <filter-class>org.springframework.web.filter.HiddenHttpMethodFilter</filter-class>
    </filter>
    <filter-mapping>
        <filter-name>HiddenHttpMethodFilter</filter-name>
        <url-pattern>/*</url-pattern>
    </filter-mapping>
  • 页面的使用方式
<form th:action="@{/user/1}" method="post" >
    <input type="hidden" name="_method" value="delete">
    <input type="submit" value="点击删除">
</form>

处理AJAX请求,使用axiso

@RequestBody(必须使用Post才有请求体)

  • 会获取当前请求的请求体,为当前注解所标识的形参赋值(注意:类型为实体类或Map

  • 使用步骤
    (1)导入Jackson依赖
    (2)添加注解驱动
    (3)在控制器的形参前标志即可

<dependency>
	<groupId>com.fasterxml.jackson.core</groupId>
	<artifactId>jackson-databind</artifactId>
	<version>2.12.1</version>
</dependency>


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

@ResponseBody

  • 将控制器方法的返回值作为响应报文直接响应给前端页面

  • 使用步骤
    (1)导入Jackson依赖
    (2)添加注解驱动
    (3)在控制器的方法上声明该注解即可

文件上传与下载

文件下载

    @RequestMapping("/testDown")
    public ResponseEntity<byte[]> testResponseEntity(HttpSession session) throws
            IOException {
        //获取ServletContext对象
        ServletContext servletContext = session.getServletContext();
        //获取服务器中文件的真实路径
        String realPath = servletContext.getRealPath("/static/img/2.jpg");
        //创建输入流
        InputStream is = new FileInputStream(realPath);
        //创建字节数组
        byte[] bytes = new byte[is.available()];
        //将流读到字节数组中
        is.read(bytes);
        //创建HttpHeaders对象设置响应头信息
        MultiValueMap<String, String> headers = new HttpHeaders();
        //设置要下载方式以及下载文件的名字
        headers.add("Content-Disposition", "attachment;filename=1.jpg");
        //设置响应状态码
        HttpStatus statusCode = HttpStatus.OK;
        //创建ResponseEntity对象
        ResponseEntity<byte[]> responseEntity = new ResponseEntity<>(bytes, headers,
                statusCode);
        //关闭输入流
        is.close();
        return responseEntity;
    }

文件上传

  • 配置文件上传解析器
    <bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver"></bean>

  • 添加依赖
        <dependency>
            <groupId>commons-fileupload</groupId>
            <artifactId>commons-fileupload</artifactId>
            <version>1.3.1</version>
        </dependency>
  • 使用示例
### 前端代码
<form th:action="@{/testUpload}" method="post" enctype="multipart/form-data">
    <input type="file" name="myFile">
    <input type="submit" value="点击上传">
</form>

### 后端代码
    @ResponseBody
    @RequestMapping("/testUpload")
    public String testUpload(MultipartFile myFile, HttpSession session) throws IOException {
        // 获取当前得到的文件名
        String oldFileName = myFile.getOriginalFilename();
        String newFileName = UUID.randomUUID() + oldFileName.substring(oldFileName.lastIndexOf("."));

        // 获取到服务器的资源路径
        ServletContext servletContext = session.getServletContext();
        String realPath = servletContext.getRealPath("/static/img");
        File file = new File(realPath);

        if (!file.exists()) {
            file.mkdirs();
        }
        // 将文件转存到服务器上
        File realFile = new File(realPath + File.separator + newFileName);
        myFile.transferTo(realFile);
        return "上传成功";
    }

拦截器

  • 用于拦截控制器方法的执行,如果配置了多个拦截器,将会按照配置的顺序执行preHandle,然后按照逆序执行postHandle和afterCompletion

使用Java代码的方式完成配置

  • 编写一个拦截器
public class FirstInterceptor implements HandlerInterceptor {

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        System.out.println("控制器方法执行之前:FirstInterceptor --> preHandle");
        return true;
    }

    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
        System.out.println("控制器方法执行之后:FirstInterceptor --> postHandle");
    }

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        System.out.println("控制器方法执行且视图渲染完毕后:afterCompletion --> afterCompletion");
    }
}
  • 完成配置
@Configuration
public class MvcExtraConfig implements WebMvcConfigurer {
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(new FirstInterceptor()).addPathPatterns("/**").excludePathPatterns("/index");
    }
}

使用XML的方式进行配置

  • 在SpringMVC中配置
    <mvc:interceptors>
        <mvc:interceptor>
            <!-- 可以直接通过bean的方式配置拦截器 <bean></bean>-->
            <!-- /* 代表一层上下文路径,/**代表所有路径 -->
            <mvc:mapping path="/**"/>
            <!-- 排除的路径 -->
            <mvc:exclude-mapping path="/testRequestEntity"/>
            <!-- 通过引用的方式配置拦截器 -->
            <ref bean="firstInterceptor"></ref>
        </mvc:interceptor>
    </mvc:interceptors>

异常处理器

SpringMVC的异常处理机制

  • 通过HandlerExceptionResolver进行项目中异常的处理,其大致的关系如下
    image

基于XML的异常处理

    <bean class="org.springframework.web.servlet.handler.SimpleMappingExceptionResolver">
        <property name="exceptionMappings">
            <props>
                <!--
                properties的键表示处理器方法执行过程中出现的异常
                properties的值表示若出现指定异常时,设置一个新的视图名称,跳转到指定页面
                -->
                <prop key="java.lang.ArithmeticException">error</prop>
            </props>
        </property>
        <!--
        exceptionAttribute属性设置一个属性名,将出现的异常信息在请求域中进行共享
        -->
        <property name="exceptionAttribute" value="ex"></property>
    </bean>

基于注解的异常处理

//@ControllerAdvice将当前类标识为异常处理的组件
@ControllerAdvice
public class DIYExceptionResolver{

    //@ExceptionHandler用于设置所标识方法处理的异常
    @ExceptionHandler(ArithmeticException.class)
    //ex表示当前请求处理中出现的异常对象
    public String handleArithmeticException(Exception ex, Model model){
        model.addAttribute("ex", ex);
        // 返回逻辑视图
        return "error";
    }
}

使用纯注解的方式代替SpringMVC配置文件和web.xml文件

  在Servlet3.0环境中,容器会在类路径中查找实现javax.servlet.ServletContainerInitializer接口的类,如果找到的话就用它来配置Servlet容器。 Spring提供了这个接口的实现,名为 SpringServletContainerInitializer,这个类反过来又会查找实现WebApplicationInitializer的类并将配置的任务交给它们来完成。Spring3.2引入了一个便利的WebApplicationInitializer基础实现,名为 AbstractAnnotationConfigDispatcherServletInitializer,当我们的类扩展了 AbstractAnnotationConfigDispatcherServletInitializer并将其部署到Servlet3.0容器的时候,容器会自动发现它,并用它来配置Servlet上下文。

编写一个WebConfig用来代替web.xml文件

import org.springframework.web.filter.CharacterEncodingFilter;
import org.springframework.web.filter.HiddenHttpMethodFilter;
import org.springframework.web.servlet.support.AbstractAnnotationConfigDispatcherServletInitializer;
import javax.servlet.Filter;

public class WebConfig extends AbstractAnnotationConfigDispatcherServletInitializer {

    /**
     * 获取Spring容器的配置
     *
     * @return
     */
    @Override
    protected Class<?>[] getRootConfigClasses() {
        return new Class[]{SpringConfig.class};
    }

    /**
     * 获取SpringMVC的配置
     *
     * @return
     */
    @Override
    protected Class<?>[] getServletConfigClasses() {
        return new Class[]{SpringmvcConfig.class};
    }

    /**
     * @return 指定DispatcherServlet的映射规则,也就是url-pattern
     */
    @Override
    protected String[] getServletMappings() {
        return new String[]{"/"};
    }

    @Override
    protected Filter[] getServletFilters() {
        CharacterEncodingFilter encodingFilter = new CharacterEncodingFilter();
        encodingFilter.setEncoding("UTF-8");
        encodingFilter.setForceRequestEncoding(true);
        HiddenHttpMethodFilter hiddenHttpMethodFilter = new
                HiddenHttpMethodFilter();
        return new Filter[]{encodingFilter, hiddenHttpMethodFilter};

    }
}

编写SpringmvcConfig代替springmvc配置文件

import com.codestars.interceptor.MyInterceptor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.context.ContextLoader;
import org.springframework.web.context.WebApplicationContext;
import org.springframework.web.multipart.commons.CommonsMultipartResolver;
import org.springframework.web.servlet.HandlerExceptionResolver;
import org.springframework.web.servlet.ViewResolver;
import org.springframework.web.servlet.config.annotation.*;
import org.springframework.web.servlet.handler.SimpleMappingExceptionResolver;
import org.thymeleaf.spring5.SpringTemplateEngine;
import org.thymeleaf.spring5.view.ThymeleafViewResolver;
import org.thymeleaf.templatemode.TemplateMode;
import org.thymeleaf.templateresolver.ITemplateResolver;
import org.thymeleaf.templateresolver.ServletContextTemplateResolver;

import java.util.List;

// 1、配置包扫描
@ComponentScan("com.codestars")
// 4、配置注解驱动
@EnableWebMvc
// 标志为一个Spring配置类
@Configuration
public class SpringmvcConfig implements WebMvcConfigurer {

    /**
     * 3、配置视图控制器
     * @param registry
     */
    @Override
    public void addViewControllers(ViewControllerRegistry registry) {
        registry.addViewController("/").setViewName("index");
    }

    /**
     * 5、配置让tomcat处理静态资源
     * @param configurer
     */
    @Override
    public void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer) {
        configurer.enable();
    }

    /**
     * 6、配置拦截器
     * @param registry
     */
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        MyInterceptor myInterceptor = new MyInterceptor();
        registry.addInterceptor(myInterceptor).addPathPatterns("/**");
    }

    /**
     * 7、配置异常处理器
     * @param resolvers
     */
    @Override
    public void extendHandlerExceptionResolvers(List<HandlerExceptionResolver> resolvers) {
        SimpleMappingExceptionResolver smer1 = new SimpleMappingExceptionResolver();
        // 配置需要处理的异常、逻辑视图、共享异常的键
        smer1.setMappedHandlerClasses(RuntimeException.class);
        smer1.setDefaultErrorView("error");
        smer1.setExceptionAttribute("ex");
        resolvers.add(smer1);
    }

    /**
     * 8、配置文件上传解析器
     * @return
     */
    @Bean
    public CommonsMultipartResolver commonsMultipartResolver() {
        return new CommonsMultipartResolver();
    }


    //配置生成模板解析器
    @Bean
    public ITemplateResolver templateResolver() {
        WebApplicationContext webApplicationContext =
                ContextLoader.getCurrentWebApplicationContext();
    // ServletContextTemplateResolver需要一个ServletContext作为构造参数,可通过WebApplicationContext 的方法获得
        ServletContextTemplateResolver templateResolver = new
                ServletContextTemplateResolver(
                webApplicationContext.getServletContext());
        templateResolver.setPrefix("/WEB-INF/templates/");
        templateResolver.setSuffix(".html");
        templateResolver.setCharacterEncoding("UTF-8");
        templateResolver.setTemplateMode(TemplateMode.HTML);
        return templateResolver;
    }
    //生成模板引擎并为模板引擎注入模板解析器
    @Bean
    public SpringTemplateEngine templateEngine(ITemplateResolver
                                                       templateResolver) {
        SpringTemplateEngine templateEngine = new SpringTemplateEngine();
        templateEngine.setTemplateResolver(templateResolver);
        return templateEngine;
    }
    //生成视图解析器并未解析器注入模板引擎
    @Bean
    public ViewResolver viewResolver(SpringTemplateEngine templateEngine) {
        ThymeleafViewResolver viewResolver = new ThymeleafViewResolver();
        viewResolver.setCharacterEncoding("UTF-8");
        viewResolver.setTemplateEngine(templateEngine);
        return viewResolver;
    }
}

配置SpringConfig代替spring的配置文件

/**
 * 暂时没有内容
 */
@Configuration
public class SpringConfig {
}

整合SSM的步骤

需要思考的问题

SpringMVC容器的创建时机与Spring的创建时机,SpringMVC的控制层需要访问Spring的业务层该怎么办

  • SpringMVC的中央控制器在配置了load-on-startup后是跟随Tomcat容器启动时进行启动的

  • SpringMVC的控制层会依赖Spring的管理的业务层,那么Spring的容器就一定要在SpringMVC创建之前进行创建。

  • 根据WEB的三大组件我们可以知道他们的执行顺序为: Listener -> Filter -> Servlet

  • 因此我们可以在Listener或Filter中来优先创建Spring的IOC容器,那么选择哪个更合适呢?

    • 首先是可以通过Filter的初始化方法来完成对SpringIOC容器的初始化,但是过滤器本来应该做的事就是过滤请求,那之后的每一个请求都会经过该过滤器,而该过滤器就啥也不干就放行?这显然不太合理

    • 因此我们考虑到了Listener,Listener一共有8个,首当其中我们选择的是ServletContextListener

  • 因此我们只要配置一个ServletContextListener在我们的Web容器初始化时创建SpringIOC的容器,并且也可以使得创建的速度比SpringMVC的IOC容器更快

  • SpringIOC容器和SpringMVC的IOC容器在创建后都会存放到Application作用域当中,当SpringMVC的IOC容器在进行创建时,会将Spring的容器作为SpringMVC容器的父容器,可以访问父容器中的组件。但是父容器不能访问子容器中的组件

正式开始整合SSM

创建一个Javaweb项目

(1)引入maven依赖(spring、springmvc、mybaits、thymeleaf、数据库驱动、连接池)

    <packaging>war</packaging>
    <properties>
        <spring.version>5.3.18</spring.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>${spring.version}</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-beans</artifactId>
            <version>${spring.version}</version>
        </dependency>
        <!--springmvc-->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-web</artifactId>
            <version>${spring.version}</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-webmvc</artifactId>
            <version>${spring.version}</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-jdbc</artifactId>
            <version>${spring.version}</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-aspects</artifactId>
            <version>${spring.version}</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-test</artifactId>
            <version>${spring.version}</version>
        </dependency>
        <!-- Mybatis核心 -->
        <dependency>
            <groupId>org.mybatis</groupId>
            <artifactId>mybatis</artifactId>
            <version>3.5.7</version>
        </dependency>
        <!--mybatis和spring的整合包-->
        <dependency>
            <groupId>org.mybatis</groupId>
            <artifactId>mybatis-spring</artifactId>
            <version>2.0.6</version>
        </dependency>
        <!-- 连接池 -->
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid</artifactId>
            <version>1.0.9</version>
        </dependency>
        <!-- junit测试 -->
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.12</version>
            <scope>test</scope>
        </dependency>
        <!-- MySQL驱动,连接数据库 -->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>8.0.16</version>
        </dependency>
        <!-- log4j日志,mybatis需要 -->
        <dependency>
            <groupId>log4j</groupId>
            <artifactId>log4j</artifactId>
            <version>1.2.17</version>
        </dependency>
        <!-- mybatis的分页插件 -->
        <dependency>
            <groupId>com.github.pagehelper</groupId>
            <artifactId>pagehelper</artifactId>
            <version>5.2.0</version>
        </dependency>
        <!-- 日志,thymeleaf依赖于slf4j,而logback是slf4j的实现 -->
        <dependency>
            <groupId>ch.qos.logback</groupId>
            <artifactId>logback-classic</artifactId>
            <version>1.2.3</version>
        </dependency>
        <!-- ServletAPI -->
        <dependency>
            <groupId>javax.servlet</groupId>
            <artifactId>javax.servlet-api</artifactId>
            <version>3.1.0</version>
            <scope>provided</scope>
        </dependency>
        <!-- ResponseBody与RequestBody依赖于jackson -->
        <dependency>
            <groupId>com.fasterxml.jackson.core</groupId>
            <artifactId>jackson-databind</artifactId>
            <version>2.12.1</version>
        </dependency>
        <!-- 文件上传 -->
        <dependency>
            <groupId>commons-fileupload</groupId>
            <artifactId>commons-fileupload</artifactId>
            <version>1.3.1</version>
        </dependency>
        <!-- Spring5和Thymeleaf整合包 -->
        <dependency>
            <groupId>org.thymeleaf</groupId>
            <artifactId>thymeleaf-spring5</artifactId>
            <version>3.0.12.RELEASE</version>
        </dependency>

        <!-- lombok插件-->
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>1.18.24</version>
            <scope>provided</scope>
        </dependency>
    </dependencies>

配置web.xml文件(2个过滤器、一个Servlet、一个监听器)

<?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">

    <!-- 1. 配置编码过滤器 -->
    <filter>
        <filter-name>CharacterEncodingFilter</filter-name>
        <filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
        <init-param>
            <param-name>encoding</param-name>
            <param-value>UTF-8</param-value>
        </init-param>
        <init-param>
            <param-name>forceEncoding</param-name>
            <param-value>true</param-value>
        </init-param>
    </filter>
    <filter-mapping>
        <filter-name>CharacterEncodingFilter</filter-name>
        <url-pattern>/*</url-pattern>
    </filter-mapping>

    <!-- 2.配置表单提交过滤器 -->
    <filter>
        <filter-name>HiddenHttpMethodFilter</filter-name>
        <filter-class>org.springframework.web.filter.HiddenHttpMethodFilter</filter-class>
    </filter>
    <filter-mapping>
        <filter-name>HiddenHttpMethodFilter</filter-name>
        <url-pattern>/*</url-pattern>
    </filter-mapping>

    <!-- 3. 配置核心Servlet接收所有的请求,不包括.jsp结尾-->
    <servlet>
        <servlet-name>DispatcherServlet</servlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
        <!-- 配置springmvc的配置文件 -->
        <init-param>
            <param-name>contextConfigLocation</param-name>
            <param-value>classpath:springmvc.xml</param-value>
        </init-param>
        <load-on-startup>1</load-on-startup>
    </servlet>
    <servlet-mapping>
        <servlet-name>DispatcherServlet</servlet-name>
        <url-pattern>/</url-pattern>
    </servlet-mapping>

    <!-- 在ContextServlet上下文启动时就加载SpringIOC容器-->
    <listener>
        <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
    </listener>

    <!-- 配置SpringIOC容器的路径 -->
    <context-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>classpath:spring.xml</param-value>
    </context-param>
</web-app>

配置springmvc.xml文件

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       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">

    <!-- 1. 扫描控制层组件 -->
    <context:component-scan base-package="com.codestars.controller"/>

    <!-- 2. 配置视图解析器ThymeleafView -->
    <bean id="viewResolver" class="org.thymeleaf.spring5.view.ThymeleafViewResolver">
        <property name="order" value="1"/>
        <property name="characterEncoding" value="UTF-8"/>
        <property name="templateEngine">
            <bean class="org.thymeleaf.spring5.SpringTemplateEngine">
                <property name="templateResolver">
                    <bean class="org.thymeleaf.spring5.templateresolver.SpringResourceTemplateResolver">
                        <!-- 视图前缀 -->
                        <property name="prefix" value="/WEB-INF/templates/"/>
                        <!-- 视图后缀 -->
                        <property name="suffix" value=".html"/>
                        <property name="templateMode" value="HTML5"/>
                        <property name="characterEncoding" value="UTF-8" />
                    </bean>
                </property>
            </bean>
        </property>
    </bean>

    <!-- 3. 配置注解驱动-->
    <mvc:annotation-driven/>

    <!-- 4. 当springmvc处理不了时,让tomcat默认servlet来处理 -->
    <mvc:default-servlet-handler/>

    <!-- 5. 配置仅用于页面跳转到view-controller,如果不配置注解驱动,将会只能访问view-controller中配置的处理器(报红不影响) -->
    <mvc:view-controller path="/" view-name="index"/>

    <!-- 6. 配置文件上传解析器 -->
    <bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver"></bean>

    <!-- 7. 配置拦截器 -->
    <!--<mvc:interceptors>-->
    <!--    <bean class=""></bean>-->
    <!--    <mvc:interceptor>-->
    <!--        &lt;!&ndash; /** 代表所有路径, /* 代表一层路径 &ndash;&gt;-->
    <!--        <mvc:mapping path="/user/**"/>-->
    <!--        &lt;!&ndash; 需要排除的路径&ndash;&gt;-->
    <!--        <mvc:exclude-mapping path=""/>-->
    <!--    </mvc:interceptor>-->
    <!--</mvc:interceptors>-->

    <!-- 8. 配置异常处理器 -->
    <bean class="org.springframework.web.servlet.handler.SimpleMappingExceptionResolver">
        <!-- 异常的逻辑视图-->
        <property name="defaultErrorView" value="error"></property>
        <!-- 定义需要处理的异常 -->
        <property name="mappedHandlerClasses">
            <array>
                <value>java.lang.Throwable</value>
            </array>
        </property>
        <!-- 请求域中错误信息的key -->
        <property name="exceptionAttribute" value="ex"></property>
    </bean>
</beans>

配置spring.xml文件

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

    <!-- 1. 配置需要加载的配置文件 -->
    <context:property-placeholder location="classpath:jdbc.properties"/>

    <!-- 2. 扫描包,排除控制层的包,因为控制层需要交给SpringMVC的IOC容器管理 -->
    <context:component-scan base-package="com.codestars">
        <context:exclude-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
    </context:component-scan>

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

    <!-- 4. 整合mybatis第一步:配置SqlSessionFactoryBean,其中的getObject可以获取到SqlSessionFactory -->
    <bean class="org.mybatis.spring.SqlSessionFactoryBean">
        <!-- 配置mybatis的全局配置文件,如果该配置文件中的属性都在该SqlSessionFactoryBean配置完毕,则该属性可以删除 -->
        <!--<property name="configLocation" value="classpath:mybatis-config.xml"></property>-->
        <!-- 配置数据源 -->
        <property name="dataSource" ref="dataSource"></property>
        <!-- 配置别名-->
        <property name="typeAliasesPackage" value="com.codestars.pojo"></property>
        <!-- 如果xml映射文件与mapper接口不在一个包下,需要配置该属性 -->
        <property name="mapperLocations" value="classpath:mappers/*.xml"></property>
        <!-- 配置分页插件 -->
        <property name="plugins">
            <array>
                <bean class="com.github.pagehelper.PageInterceptor">
                    <property name="properties">
                        <props>
                            <!-- 配置合理化页码 -->
                            <prop key="reasonable">true</prop>
                        </props>
                    </property>
                </bean>
            </array>
        </property>
        <!-- 配置SessionSqlFactory的全局setting -->
        <property name="configurationProperties">
            <props>
                <!-- 延迟加载 -->
                <prop key="lazyLoadingEnabled">true</prop>
                <prop key="aggressiveLazyLoading">false</prop>
                <!-- 二级缓存 -->
                <prop key="cacheEnabled">true</prop>
                <!-- 数据库下划线与实体类驼峰转换 -->
                <prop key="mapUnderscoreToCamelCase">true</prop>
            </props>
        </property>
    </bean>

    <!--
        4. 整合Mybatis第二步:获取第一步配置的SqlSessionFactory拿到所有Mapper接口映射的代理对象,
        然后交给SpringIOC容器管理(非常重要)
    -->
    <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
        <property name="basePackage" value="com.codestars.mapper"></property>
    </bean>

    <!-- 5. 开启基于注解的aop功能 -->
    <aop:aspectj-autoproxy/>

    <!-- 6. 配置声明式事物 -->
    <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <property name="dataSource" ref="dataSource"></property>
    </bean>

    <!-- 7. 配置使@Transactional标识的类或方法成为连接点 -->
    <tx:annotation-driven/>

    <!-- 基于XML的AOP配置-->
    <!--<aop:config>-->
    <!--    <aop:pointcut id="pointCut" expression="execution(* com.codestars.service.impl.*.*(..))"/>-->
    <!--    <aop:aspect ref="">-->
    <!--        <aop:before method=""></aop:before>-->
    <!--        <aop:after method=""></aop:after>-->
    <!--        <aop:after-returning method=""></aop:after-returning>-->
    <!--        <aop:after-throwing method=""></aop:after-throwing>-->
    <!--    </aop:aspect>-->
    <!--</aop:config>-->

</beans>

配置log4j.xml文件

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE log4j:configuration SYSTEM "log4j.dtd">
<log4j:configuration xmlns:log4j="http://jakarta.apache.org/log4j/">
    <appender name="STDOUT" class="org.apache.log4j.ConsoleAppender">
        <param name="Encoding" value="UTF-8"/>
        <layout class="org.apache.log4j.PatternLayout">
            <param name="ConversionPattern" value="%-5p %d{MM-dd HH:mm:ss,SSS}
%m (%F:%L) \n"/>
        </layout>
    </appender>
    <logger name="java.sql">
        <level value="debug"/>
    </logger>
    <logger name="org.apache.ibatis">
        <level value="info"/>
    </logger>
    <root>
        <level value="debug"/>
        <appender-ref ref="STDOUT"/>
    </root>
</log4j:configuration>

配置jdbc.properties文件

jdbc.driver=com.mysql.cj.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/ssm?useSSL=false&useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai
jdbc.username=root
jdbc.password=abc123
# 初始连接数量
jdbc.initialSize=5
# 最大连接数量
jdbc.maxActive=10
# 最大等待时间,单位毫秒
jdbc.maxWait=3000

配置mybatis-config.xml文件(可以不需要)

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
        PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
    <!-- 各个不同配置的顺序
        properties?,settings?,typeAliases?,typeHandlers?,objectFactory?,objectWrapperFactory?,
        reflectorFactory?,plugins?,environments?,databaseIdProvider?,mappers?
    -->

    <!-- 引入外部配置文件,从根路径下找 -->
    <properties resource="jdbc.properties"/>

    <settings>
        <!--开启延迟加载功能-->
        <setting name="lazyLoadingEnabled" value="true"/>
        <!-- 关闭该功能后,才能进行按需加载,与lazyLoadingEnabled 配合使用-->
        <setting name="aggressiveLazyLoading" value="false"/>

        <!-- 开启二级缓存,作用范围为一个SqssionFactory -->
        <setting name="cacheEnabled" value="true"/>

        <!-- 配置数据库字段下划线与JAVA实体类的驼峰映射 -->
        <setting name="mapUnderscoreToCamelCase" value="true"/>
    </settings>

    <!-- 配置别名 -->
    <typeAliases>
        <package name="com.codestarts.pojo"/>
    </typeAliases>

    <plugins>
        <!--设置分页插件-->
        <plugin interceptor="com.github.pagehelper.PageInterceptor"></plugin>
    </plugins>

    <!-- 配置一个个的环境,其中主要配置了事务管理的方式和数据源 -->
    <environments default="development">
        <environment id="development">
            <!--
                配置采用的事务管理方式,JDBC:编程式事务管理,
                MANAGER:啥也不干,让容器来管理事务的整个生命周期
            -->
            <transactionManager type="JDBC"/>
            <!--
                POOLED:采用数据库连接池的方式来获取连接
                UNPOOLED:每一个连接都直接通过访问数据库获得
                JNDI :上下文数据源引用(了解)
            -->
            <dataSource type="POOLED">
                <property name="driver" value=""/>
                <property name="url" value=""/>
                <property name="username" value=""/>
                <property name="password" value=""/>
            </dataSource>
        </environment>
        <!-- 可以配置多个,自行选择 -->
        <environment id="test">
            <transactionManager type="JDBC"/>
            <dataSource type="POOLED">
                <property name="driver" value=""/>
                <property name="url" value=""/>
                <property name="username" value=""/>
                <property name="password" value=""/>
            </dataSource>
        </environment>
    </environments>

    <mappers>
        <!-- 配置单个的方式mappers/UserMapper.xml代表根路径下的mappers/UserMapper -->
        <!--<mapper resource="mappers/UserMapper.xml"></mapper>-->

        <!--
            配置mapper接口与mapper.xml所在的包,
            注意mapper接口和mapper.xml的名字必须一致
            注意接口与mapper必须在同一个包下
        -->
        <package name="com.codestarts.mapper"/>
    </mappers>

</configuration>

最终项目目录结构

image

posted @ 2022-08-31 11:26  CodeStars  阅读(76)  评论(0编辑  收藏  举报