三阶段

MyBatis

1 三层架构介绍

2 MyBatis介绍

类库:对于现有技术的一个封装。

框架:对于一个问题的一整套解决方案。

MyBatis是一个半自动的ORM持久层的框架。刚开始叫做iBatis。代码迁移到google后改名MyBatis。

优点:

  • 自动封装数据到实体类。
  • 有多种动态标签,实现动态sql。
  • sql语句可以和代码分开,降低耦合性。
  • 可以面向接口开发,并且不用写接口的实现类。

3 MyBatis入门案例

  • 建库建表

  • 创建项目

  • 导入依赖

<dependencies>
<!--        mybatis框架-->
        <dependency>
            <groupId>org.mybatis</groupId>
            <artifactId>mybatis</artifactId>
            <version>3.5.4</version>
        </dependency>

        <!--mysql驱动包-->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>8.0.28</version>
        </dependency>
    </dependencies>
  • 生成实体类
package com.javasm.entity;


public class User {

  private long id;
  private String username;
  private java.util.Date birthday;
  private String sex;
  private String address;


  public long getId() {
    return id;
  }

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


  public String getUsername() {
    return username;
  }

  public void setUsername(String username) {
    this.username = username;
  }


  public java.util.Date getBirthday() {
    return birthday;
  }

  public void setBirthday(java.util.Date birthday) {
    this.birthday = birthday;
  }


  public String getSex() {
    return sex;
  }

  public void setSex(String sex) {
    this.sex = sex;
  }


  public String getAddress() {
    return address;
  }

  public void setAddress(String address) {
    this.address = address;
  }


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

  • 从官网上拷贝核心配置文件的内容
<?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">
<!--mybatis的核心配置文件-->
<configuration>
    
    <settings>
<!--        开启日志-->
        <setting name="logImpl" value="STDOUT_LOGGING"/>
    </settings>
    
    <environments default="development">
        <environment id="development">
            <transactionManager type="JDBC"/>
            <dataSource type="POOLED">
                <property name="driver" value="com.mysql.cj.jdbc.Driver"/>
                <property name="url" value="jdbc:mysql:///mybatis?serverTimezone=Asia/Shanghai"/>
                <property name="username" value="root"/>
                <property name="password" value="root"/>
            </dataSource>
        </environment>
    </environments>
    <mappers>
<!--        从类路径下一级级找,并且用/隔开-->
        <mapper resource="UserDao.xml"/>
    </mappers>
</configuration>
  • 从官网拷贝映射文件
<?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="">


</mapper>
  • 在映射文件中编写对应的sql语句
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<!--namespace称之为命名空间   对于不同的映射文件可以分类  相当于包的功能-->
<!--在一个namespace中去编写sql语句-->
<mapper namespace="gufengshuo">

<!--
    select     <select>
    insert     <insert>
    update     <update>
    delete     <delete>
-->

<!--    根据id查询用户
    id:是当前sql语句的唯一标识
    parameterType:参数类型 如果是基本数据类型,可以直接用别名   如果是自定义类型,需要写全路径名
    resultType:返回值类型  如果是基本数据类型,可以直接用别名   如果是自定义类型,需要写全路径名
-->
    <select id="findUserById" parameterType="int" resultType="com.javasm.entity.User">
        select * from user where id = #{id}
    </select>

<!--    添加用户
    keyProperty:主键的名称
    order: 取值是after或before  就是在sql语句执行之前或之后执行last_insert_id()函数
    resultType:返回值类型

    selectKey标签是添加主键时返回主键的值
-->
    <insert id="addUser" parameterType="com.javasm.entity.User">

        <selectKey keyProperty="id" order="AFTER" resultType="int">
            select last_insert_id();
        </selectKey>
        insert into user (username,birthday,sex,address) values(#{username},#{birthday},#{sex},#{address})
    </insert>

<!--    修改用户-->
    <update id="updateUser" parameterType="com.javasm.entity.User">
        update user set username = #{username}, birthday = #{birthday},sex = #{sex},address = #{address} where id = #{id}
    </update>

<!--    根据id删除用户-->
    <delete id="deleteUserById" parameterType="int">
        delete from user where id = #{id}
    </delete>

<!--    模糊查询
  ${}和#{}的区别?
  ${} 就算传递的是一个字符串,也去掉引号后拼接到sql语句中
  #{} 如果传递的是一个字符串,那么直接以字符串的形式拼接到sql中
-->
    <select id="searchUser" parameterType="string" resultType="com.javasm.entity.User">
        select * from user where username like '%${value}%'
    </select>
</mapper>
  • 通过测试类进行测试
package com.shangma.test;

import com.javasm.entity.User;
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
import org.junit.Test;

import java.io.IOException;
import java.io.InputStream;
import java.util.Date;
import java.util.List;

/**
 * @author: ShangMa
 * @className: TestDemo
 * @description:
 * @date: 2022/8/1 11:16
 */
public class TestDemo {

    @Test
    public void test01() throws IOException {
        // 加载核心配置文件  这里是从类路径开始一级级往下找,文件夹用/隔开
        String resource = "abc.xml";
        InputStream inputStream = Resources.getResourceAsStream(resource);
        // 获取SqlSession的工厂对象
        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
        // 获取SqlSession对象   openSession的参数如果是true,那么会自动提交事务
        SqlSession sqlSession = sqlSessionFactory.openSession(true);
        // 使用SqlSession执行增删改查
        User user = sqlSession.selectOne("gufengshuo.findUserById", 10);
        System.out.println(user);
        // 关闭资源
        sqlSession.close();
    }

    // 添加用户
    @Test
    public void test02() throws IOException {
        // 加载核心配置文件  这里是从类路径开始一级级往下找,文件夹用/隔开
        String resource = "abc.xml";
        InputStream inputStream = Resources.getResourceAsStream(resource);
        // 获取SqlSession的工厂对象
        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
        // 获取SelSession对象
        SqlSession sqlSession = sqlSessionFactory.openSession(true);
        User user = new User();
        user.setUsername("珠儿");
        user.setAddress("灵蛇岛");
        user.setBirthday(new Date());
        user.setSex("女");
        // 默认已经开启了事务,那么在增删改时需要提交事务
        sqlSession.insert("gufengshuo.addUser",user);
        // 提交事务
//        sqlSession.commit();
        sqlSession.close();
    }

    // 修改用户
    @Test
    public void test03() throws IOException {
        // 加载核心配置文件  这里是从类路径开始一级级往下找,文件夹用/隔开
        String resource = "abc.xml";
        InputStream inputStream = Resources.getResourceAsStream(resource);
        // 获取SqlSession的工厂对象
        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
        // 获取SelSession对象
        SqlSession sqlSession = sqlSessionFactory.openSession();

        // 根据id查询一个用户
        User user = sqlSession.selectOne("gufengshuo.findUserById", 26);
        user.setUsername("小昭");
        user.setSex("女");
        user.setBirthday(new Date());
        user.setAddress("阿富汗");

        // 更新用户
        sqlSession.update("gufengshuo.updateUser",user);
        // 提交事务
//        sqlSession.commit();

        sqlSession.close();
    }

    @Test
    public void test04() throws IOException {
        // 加载核心配置文件  这里是从类路径开始一级级往下找,文件夹用/隔开
        String resource = "abc.xml";
        InputStream inputStream = Resources.getResourceAsStream(resource);
        // 获取SqlSession的工厂对象
        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
        // 获取SelSession对象
        SqlSession sqlSession = sqlSessionFactory.openSession();
        // 删除用户
        sqlSession.delete("gufengshuo.deleteUserById",22);
        // 提交事务
//        sqlSession.commit();
        // 关闭资源
        sqlSession.close();
    }

    @Test
    public void test05() throws IOException {
        // 加载核心配置文件  这里是从类路径开始一级级往下找,文件夹用/隔开
        String resource = "abc.xml";
        InputStream inputStream = Resources.getResourceAsStream(resource);
        // 获取SqlSession的工厂对象
        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
        // 获取SelSession对象
        SqlSession sqlSession = sqlSessionFactory.openSession();
        // 模糊查询
        List<User> users = sqlSession.selectList("gufengshuo.searchUser", "张");
        users.forEach(user-> System.out.println(user));

        sqlSession.close();
    }
}

4 ${}和#{}的区别

${} 就算传递的是字符串类型,那么在拼接sql语句时依然会去掉双引号,按照本身的类型进行拼接

{} 传递的如果是一个字符串,那么会以字符串的形式进行拼接

5 开启日志

在核心配置文件中添加配置

<settings>
        <!--        开启日志-->
        <setting name="logImpl" value="STDOUT_LOGGING"/>
    </settings>

6 在Dao层中使用MyBatis(了解)

定义接口,提供方法

public interface UserDao {

    void addUser(User user);

    List<User> findAll();
}

定义实现类,实现方法

public class UserDaoImpl implements UserDao {

    private SqlSession sqlSession;

    public UserDaoImpl(SqlSession sqlSession){
        this.sqlSession = sqlSession;
    }

    @Override
    public void addUser(User user) {
        sqlSession.insert("gufengshuo.addUser",user);
    }

    @Override
    public List<User> findAll() {
        return sqlSession.selectList("gufengshuo.findAll");
    }
}

需要在xml中提供对应的sql语句。

7 Mapper接口的开发(重点)

通过Mapper接口代理对象,可以实现不使用实现类,直接面向接口就可以进行增删改查。

7.1 Mapper接口使用的书写规范

  • 映射文件中的namespace应该是mapper接口的全路径名
  • 映射文件中的id必须和接口中的方法名相同
  • 接口中方法的参数只能有一个,并且映射文件中的parameterType要和方法的参数类型一致
  • 接口中方法的返回值要和resultType中的类型一致

7.2 编写代码

定义mapper接口,提供抽象方法

public interface UserMapper {

    User findUserById(Integer id);

    void updateUser(User user);

    void deleteUserById(Integer id);
}

根据书写的规范去编写映射文件

<?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.javasm.mapper.UserMapper">
<!--    Mapper接口的书写规范
- 映射文件中的namespace应该是mapper接口的全路径名
- 映射文件中的id必须和接口中的方法名相同
- 接口中方法的参数只能有一个,并且映射文件中的parameterType要和方法的参数类型一致
- 接口中方法的返回值要和resultType中的类型一致

-->

    <select id="findUserById" parameterType="int" resultType="com.javasm.entity.User">
        select * from user where id = #{id}
    </select>

    <update id="updateUser" parameterType="com.javasm.entity.User">
        update user set username = #{username},sex = #{sex},address = #{address},birthday = #{birthday} where id = #{id}
    </update>

    <delete id="deleteUserById" parameterType="int">
        delete from user where id = #{id}
    </delete>
</mapper>

在测试时获取的是mapper接口的代理对象

package com.shangma.test;

import com.javasm.entity.User;
import com.javasm.mapper.UserMapper;
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;

import java.io.IOException;
import java.io.InputStream;

/**
 * @author: ShangMa
 * @className: TestDemo2
 * @description:
 * @date: 2022/8/1 15:32
 */
public class TestDemo2 {

    private SqlSession sqlSession;

    private UserMapper userMapper;

    @Before
    public void before() throws IOException {
        // 加载核心配置文件  这里是从类路径开始一级级往下找,文件夹用/隔开
        String resource = "abc.xml";
        InputStream inputStream = Resources.getResourceAsStream(resource);
        // 获取SqlSession的工厂对象
        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
        // 获取SqlSession对象   openSession的参数如果是true,那么会自动提交事务
        sqlSession = sqlSessionFactory.openSession(true);
        // 获取mapper接口的代理对象
        userMapper = sqlSession.getMapper(UserMapper.class);
    }

    @After
    public void release(){
        sqlSession.close();
    }

    @Test
    public void test01(){
        User userById = userMapper.findUserById(30);
        System.out.println(userById);
    }

}

8 全局配置文件讲解

8.1 命名的规范

文件名称可以随便命名,但是在公司中一般使用两个名称

  • sqlMapperConfig.xml
  • mybatis-config.xml

8.2 全局配置文件内容

<?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">
<!--mybatis的核心配置文件-->
<configuration>

<!--    加载外部文件-->
    <properties resource="db.properties"/>

    <settings>
        <!--        开启日志-->
        <setting name="logImpl" value="STDOUT_LOGGING"/>
    </settings>

<!--    给实体类起别名
    type:需要起别名的类型
    alias:起的别名
-->
    <typeAliases>
<!--        对于某一个实体类起别名-->
<!--        <typeAlias type="com.javasm.entity.User" alias="user"></typeAlias>-->
<!--        对于整个包起别名   别名就是类名 不区分大小写  今日推荐-->
        <package name="com.javasm.entity"/>
    </typeAliases>

<!--
environments   环境
  environment:具体的某一个环境
   default:使用哪一套环境的id

   一般在公司中开发时会有三套环境
   开发环境:程序员编写源代码的环境
   测试环境:测试人员在进行测试时使用的环境
   线上环境:真正公司用户使用的环境
   id:对于某一个环境起一个唯一标识
   -->
    <environments default="development">
        <environment id="development">
<!--            jdbc的事务管理交给mybatis-->
            <transactionManager type="JDBC"/>
<!--            数据源设置
                type:数据源使用的类型  取值有POOLED  和UNPOOLED
                POOLED:使用PooledDataSource进行连接池的使用
                UNPOOLED:使用的是UnpooledDataSource,没有连接池
-->
            <dataSource type="POOLED">
                <property name="driver" value="${jdbc.driver}"/>
                <property name="url" value="${jdbc.url}"/>
                <property name="username" value="${jdbc.username}"/>
                <property name="password" value="${jdbc.password}"/>
            </dataSource>
        </environment>
    </environments>
    <mappers>
<!--    对应到类路径下的xml文件  从类路径下一级级找,并且用/隔开-->
<!--        <mapper resource="com/UserDao.xml"/>-->
<!--        <mapper resource="UserMapper.xml"/>-->
<!--        url放入的是xml文件的绝对路径,并且需要加上文件的协议-->
<!--        <mapper url="file:///E:\workspace_two\mybatis-day1-demo1\src\main\resources\com\UserDao.xml"></mapper>-->
<!--        class中放入的是接口的路径
        注意点:class的这种方式使用要求是接口和xml文件需要同名同目录
-->
<!--        <mapper class="com.javasm.mapper.UserMapper"></mapper>-->

<!--
    将整个包的文件加载到内存中
    要求依然是接口和xml文件同名同目录   
    今日推荐!!
-->
        <package name="com.javasm.mapper"/>
    </mappers>

</configuration>

9 映射文件讲解

9.1 parameterType

  • 基本类型
<!--
    parameterType 取值可以是基本类型 自定义类型 包装类型 Map类型

    在使用基本类型时,比如int  可以使用int  也可以使用java.lang.Integer   string  java.lang.String
    实际上系统已经对于常用的类型起了别名  别名可以去查看TypeAliasRegistry类
-->
    <select id="findUserById" parameterType="int" resultType="UsER">
        select * from user where id = #{id}
    </select>
  • 自定义类型
<!--    paramaterType中的类型如果没有起别名,那么必须要使用全路径名 -->
    <insert id="addUser" parameterType="user">
        insert into user (username,birthday,sex,address) values(#{username},#{birthday},#{sex},#{address})
    </insert>
  • 嵌套类型
<select id="searchUser" parameterType="userexten" resultType="user">
        select * from user where sex = #{user.sex} and username like '%${like}%'
    </select>
  • map类型
<!--    #{}和${}可以获取实体类中属性名的值
        也可以获取Map中key对应的值
-->
    <select id="searchUser1" parameterType="hashmap" resultType="user">
        select * from user where username like '%${firstName}%' and sex = #{sex}
    </select>

注意:parameterType可以省略

9.2 resultType

注意:如果返回值类型是集合,那么resultType中使用的是集合中的类型

如果表中的字段和实体类中的属性名一致,mybatis会自动把值映射到实体类对象中

如果表中的字段和实体类中符合驼峰命名,可以开启驼峰命名映射

<!--        开启驼峰命名映射-->
        <setting name="mapUnderscoreToCamelCase" value="true"/>

9.3 resultMap

如果表中的字段和实体类完全对应不上,可以使用手动结果映射

<!--
   手动映射结果集
   id:resultMap的唯一标识
   type:需要映射的类型
   column:表中的字段名
   property:实体类中的属性名
   id标签对应的是主键
   result标签对应的是普通字段
-->
    <resultMap id="resultmap" type="user">
        <id column="uid" property="userId"></id>
        <result column="abc" property="userAddress"></result>
    </resultMap>
    <select id="findUserById" resultMap="resultmap">
        <include refid="selectAll"></include> where uid = #{id}
    </select>

10 动态sql

<?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.shangma.mapper.UserMapper">

<!--    select * from user where username = ? and sex = ? and address = ? and birthday = ? -->
    <select id="searchUser" resultType="User">
        select * from user
        <where>
            <if test="username != null and username != ''">
                 and username = #{username}
            </if>
            <if test="sex != null and sex != ''">
                and sex = #{sex}
            </if>
            <if test="address != null and address != ''">
                and address = #{address}
            </if>
            <if test="birthday != null">
                and birthday = #{birthday}
            </if>
            </where>
    </select>

<!--    更新用户
 update user set username = #{username}, sex = #{sex}... where id = #{id}
-->
    <update id="updateUser">
        update user
        <set>
            <if test="username != null and username !=''">
                username = #{username},
            </if>
            <if test="sex != null and sex != ''">
                sex = #{sex},
            </if>
            <if test="address != null and address != ''">
                address = #{address},
            </if>
            <if test="birthday != null">
                birthday = #{birthday},
            </if>
            </set>
        where id = #{id}
    </update>

<!--    添加用户
  insert into user (username,sex,address,birthday) values(#{username},#{sex},#{address},#{birthday})
-->
    <insert id="addUser">
        insert into user
        <trim prefix="(" suffix=")" suffixOverrides=",">
            <if test="username != null and username !=''">
                username,
            </if>
            <if test="sex != null and sex !=''">
                sex,
            </if>
            <if test="address != null and address !=''">
                address,
            </if>
            <if test="birthday != null">
                birthday,
            </if>
        </trim>
        values
        <trim prefix="(" suffix=")" suffixOverrides=",">
            <if test="username != null and username != ''">
                #{username},
            </if>
            <if test="sex != null and sex != ''">
                #{sex},
            </if>
            <if test="address != null and address != ''">
                #{address},
            </if>
            <if test="birthday != null">
                #{birthday}
            </if>
        </trim>
    </insert>

<!--
    删除用户
    delete from user where username = #{username} and sex = #{sex}...
-->
    <delete id="deleteUser">
        delete from user
        <where>
            <if test="username != null and username != ''">
                and username = #{username}
            </if>
            <if test="sex != null and sex != ''">
                and sex = #{sex}
            </if>
            <if test="address != null and address != ''">
                and address = #{address}
            </if>
            <if test="birthday != null">
                and birthday = #{birthday}
            </if>
        </where>
    </delete>

<!--
    delete from user where id in (31,3,2)
    collection:需要遍历的集合类型
    open:元素左边的符号
    close:元素右边的符号
    separator:元素之间分割的符号
    item:元素的名称
-->
    <delete id="deleteUsers">
        delete from user where id in
        <foreach collection="list" open="(" close=")" item="id" separator=",">
            #{id}
        </foreach>
    </delete>
</mapper>

11 多表设计

关系型数据库中以表来代表现实中的事物。表中的一条记录代表一条数据。表和表之间的关系来代表现实中的事物之间的关系。表和表之间的关系通过外键来关联。在一张表中存储另一张表的主键作为外键。

11.1 一对一

11.2 一对多

11.3 多对多

1659411897835

11.4 多表查询

  • 笛卡儿积查询
select * from dept,emp;
查出的结果是两张表的乘积
  • 内连接查询(查询两张表中都有的数据)
select * from dept,emp whee dept.id = emp.did;
select * from dept inner join emp on dept.id = emp.did
  • 外连接查询

    • 左外连接(左边有,右边没有)
    select * from dept left join emp on dept.id = emp.did
    
    • 右外连接(右边有,左边没有)
    select * from dept right join emp on dept.id = emp.did
    
    • 全外连接(全部都有 mysql不支持)
    select * from dept full join emp on dept.id = emp.did
    
    select * from dept left join emp on dept.id = emp.did 
    union 
    select * from dept right join emp on dept.id = emp.did
    

12 使用MyBatis进行多表查询

一个用户对应一个购物车,一个购物车对应一个用户 1对1

一个购物车对应多个购物车项 一对多

一个购物车项对应一个商品 一对一

12.1 一对一查询 (association)

在UserMapper中添加抽象方法

/**
     * 查询用户和购物车信息
     */
    List<User> selectUserAndUserCart();

在UserMapper.xml中手动进行结果的映射

    <!--    type:需要手动映射的类型-->
    <resultMap id="userAndCart" type="user">
        <id property="id" column="id"></id>
        <result property="address" column="address"></result>
        <result property="sex" column="sex"></result>
        <result property="username" column="username"></result>
        <result property="birthday" column="birthday"></result>
        <!--        association 是针对一对一场景下的手动映射-->
        <association property="cart" javaType="cart">
            <id property="cartId" column="cartId"></id>
            <result property="userId" column="userId"></result>
            <result property="totalnum" column="totalnum"></result>
            <result property="totalmoney" column="totalmoney"></result>
        </association>
    </resultMap>

    <select id="selectUserAndUserCart" resultMap="userAndCart">
        select * from user u,cart c where u.id = c.userId
    </select>

12.2 一对多查询(collection)

创建CartMapper接口,添加方法

/**
     * 查询购物车和购物车项
     */
    List<Cart> selectCartAndCartitem();

在CartMapper.xml中添加手动结果映射的内容

    <!--    查询购物车和购物车中的项
    select * from cart c,cartitem ci where c.cartId = ci.cartId
    -->

    <resultMap id="cartAndCartItem" type="cart">
        <id property="cartId" column="cartId"></id>
        <result property="userId" column="userId"></result>
        <result property="totalnum" column="totalnum"></result>
        <result property="totalmoney" column="totalmoney"></result>
        <!--    collection 针对一对多的手动映射    -->
        <collection property="cartitems" ofType="cartitem">
            <id column="cartItemId" property="cartItemId"></id>
            <result column="cartId" property="cartId"></result>
            <result column="pid" property="pid"></result>
            <result column="pnum" property="pnum"></result>
            <result column="pmoney" property="pmoney"></result>
        </collection>
    </resultMap>
    <select id="selectCartAndCartitem" resultMap="cartAndCartItem">
        select * from cart c,cartitem ci where c.cartId = ci.cartId
    </select>

12.3 多对多查询

在UserMapper中添加抽象方法

/**
     * 查询用户和商品信息
     */
    List<User> selectUserAndGood();

在UserMapper.xml中添加手动结果映射

 <!--    查询用户的商品信息
    select * from user u,cart c,cartitem ci,good g
    where
    u.id = c.userId
    and
    c.cartId = ci.cartId
    and
    ci.pid = g.pid
    -->
    <resultMap id="userAndGood" type="user">
        <id property="id" column="id"></id>
        <result property="address" column="address"></result>
        <result property="sex" column="sex"></result>
        <result property="username" column="username"></result>
        <result property="birthday" column="birthday"></result>
        <!--        association 是针对一对一场景下的手动映射-->
        <association property="cart" javaType="cart">
            <id property="cartId" column="cartId"></id>
            <result property="userId" column="userId"></result>
            <result property="totalnum" column="totalnum"></result>
            <result property="totalmoney" column="totalmoney"></result>
            <!--    collection 针对一对多的手动映射    -->
            <collection property="cartitems" ofType="cartitem">
                <id column="cartItemId" property="cartItemId"></id>
                <result column="cartId" property="cartId"></result>
                <result column="pid" property="pid"></result>
                <result column="pnum" property="pnum"></result>
                <result column="pmoney" property="pmoney"></result>
                <association property="good" javaType="good">
                    <id column="pid" property="pid"></id>
                    <result column="pname" property="pname"></result>
                    <result column="price" property="price"></result>
                    <result column="pimg" property="pimg"></result>
                    <result column="pdesc" property="pdesc"></result>
                </association>
            </collection>
        </association>
    </resultMap>
    <select id="selectUserAndGood" resultMap="userAndGood">
        select * from user u,cart c,cartitem ci,good g
        where
         u.id = c.userId
         and
         c.cartId = ci.cartId
         and
         ci.pid = g.pid
    </select>

13 延迟加载

延迟加载又称之为懒加载(lazy load)
在关联的查询中,当真正使用的关联数据时才去加载。如果没有使用到关联数据,就先不加载。
可以提交数据库的性能和效率。
如果只是一条sql语句,没有什么延迟加载可言。延迟加载需要对sql语句进行拆分。
mybatis提供的association和collection具有延迟加载的功能

在UserMapper中添加抽象方法

/*
    使用延迟加载的方式查询用户和购物车信息
     */
    List<User> selectUserAndUserCartByLazyLoad();

在UserMapper.xml中添加

<!--
 select:需要延迟加载的方法     必须要写  全路径名
 column: 表示将下面select * from user这个sql语句的哪一列当作参数传递给select中的方法
-->
    <resultMap id="lazyMap" type="user">
        <id property="id" column="id"></id>
        <result property="address" column="address"></result>
        <result property="sex" column="sex"></result>
        <result property="username" column="username"></result>
        <result property="birthday" column="birthday"></result>
        <association property="cart" javaType="cart" select="com.shangma.mapper.CartMapper.selectCartById" column="id">
        </association>
    </resultMap>

    <select id="selectUserAndUserCartByLazyLoad" resultMap="lazyMap">
        select * from user
    </select>

CartMapper接口

/**
     * 通过id查询购物车
     */
    Cart selectCartById(int userId);

CartMapper.xml中


    <select id="selectCartById" resultType="cart">
        select * from cart where userId = #{id}
    </select>

14 mybatis的缓存

/**
     * 什么是缓存?
     * 把热门数据保存在内存中的容器中,每次请求时先从容器中获取,如果获取不到再去数据库中查询。这个容器就可以称之为缓存。
     *
     * 缓存优点:
     * 提高了查询的效率
     * 降低了数据库的压力
     * 提高了用户的体验
     *
     * 缓存弊端:
     * 数据可能不是最新的
     *
     * 定时清空缓存
     * 执行DML语句就更新缓存
     * 使用缓存增删改查,定时保存到数据库
     *
     *
     * mybatis框架中提供了一级缓存和二级缓存
     *
     * 一级缓存默认就是开启的。是sqlSession(事务)级别的缓存。
     * 一级缓存没有失效时间,但是有生命周期(执行DML语句时会清空缓存)
     * 一级缓存使用的是map集合
     * map中的key就是sql语句和条件等信息组成的唯一值
     * map中的value就是查询出的结果
     *
     *
     * 二级缓存
     * 默认是关闭的
       二级缓存是namespace或者是mapper接口或者是sqlSessionFactory级别的缓存
       二级缓存可以在不同的sqlSession(事务)中有缓存。
       当所属的namespace使用增删改时,会清空对应namespace的缓存
       二级缓存有可能存在内存中,也有可能存在硬盘中
     */

    @Test
    public void fun01(){
        // 验证一级缓存
        // 保证mapper来自于同一个sqlSession
        CartMapper mapper1 = sqlSession.getMapper(CartMapper.class);
        System.out.println(mapper1.selectCartById(4));

        CartMapper mapper2 = sqlSession.getMapper(CartMapper.class);
        System.out.println(mapper2.selectCartById(4));

        CartMapper mapper3 = sqlSession.getMapper(CartMapper.class);
        System.out.println(mapper3.selectCartById(4));
    }

    @Test
    public void fun02(){
        SqlSession sqlSession = sqlSessionFactory.openSession(true);
        SqlSession sqlSession1 = sqlSessionFactory.openSession(true);
        SqlSession sqlSession2 = sqlSessionFactory.openSession(true);

        CartMapper cartMapper1 = sqlSession.getMapper(CartMapper.class);
        System.out.println(cartMapper1.selectCartById(4));
        sqlSession.close();

        CartMapper cartMapper2 = sqlSession1.getMapper(CartMapper.class);
        System.out.println(cartMapper2.selectCartById(4));
        sqlSession1.close();

        CartMapper cartMapper3 = sqlSession2.getMapper(CartMapper.class);
        System.out.println(cartMapper3.selectCartById(4));
        sqlSession2.close();
    }

15 注解方式使用mybatis

@CacheNamespace //开启二级缓存
public interface TestMapper {

    /**
     *  查询所有用户
     */
    @Select("select * from user")
    @Results(
            {
                    @Result(column = "uid",property = "id"),
                    @Result(column = "user_address",property = "userAddress")
            }
    )
    List<User> selectAll();

    /**
     * 根据id查询
     */
    @Select("select * from user where id = #{id}")
    User selectUserById(Integer id);

    /**
     * 添加用户
     */
    @Insert("insert into user (username,sex,address,birthday) values(#{username},#{sex},#{address},#{birthday})")
    void addUser(User user);

    /**
     * 修改用户
     */
    @Update("update user set username = #{username},sex= #{sex},address = #{address},birthday = #{birthday} where id = #{id}")
    void udpateUser(User user);

    /**
     * 根据id删除用户
     */
    @Delete("delete from user where id = #{id}")
    void deleteUserById(Integer id);
}


Spring

2 Spring介绍

spring直译过来就是春天。今天讲解的是spring framework。

spring framework中主要有ioc和aop两大技术亮点。还有一个声明式事务。

优点:

  • 可以降低层与层之间的耦合
  • 可以提高代码的内聚
  • 声明式事务
  • Spring可以非常方便的集成第三方框架
  • 降低了java的API难度

spring是java经典三层架构中的大管家,在每一层中都有体现

3 Spring体系中的结构图

1659497322990

4 Spring的控制反转(IOC)

4.1 什么是控制反转?

控制反转(IOC),简单来说就是将对象的创建和生命周期管理的权利交由spring框架来处理。从此在开发中不需要在关注这些细节,而是在需要获取对象时直接从spring容器中获取就可以,这样的机制称之为控制反转。好处就是可以解耦。

4.2 使用Spring

  • 导包
<dependencies>
<!--        如果是导入jar包,需要导入4核1日包-->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>5.1.9.RELEASE</version>
        </dependency>
    </dependencies>

  • 创建spring的核心配置文件

  • 配置bean

<!--    通过spring的配置来创建对象
        class:需要被spring容器管理的对象的类型(全路径名)
        id:spring容器中的唯一标识
-->
    <bean class="com.shangma.entity.Person" id="person"></bean>
  • 测试
// 加载spring核心配置文件
        ApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml");
        // 从spring容器中获取对应id的对象   Person p = map.get(id);
        Person person = (Person) applicationContext.getBean("person");
        System.out.println(person);

4.3 Spring ioc的启动原理

在启动程序时先去加载核心配置文件,如果发现bean标签,那么就把bean标签中的class和id的值解析出来。然后根据反射技术创建对象,并且把对象按照id作为key存入spring容器中(map)。当去获取对象时,是从map中根据id作为key获取value

Class clz = Class.forName("全路径名");
Person p = clz.newInstance();
map.put(id,p);

Person p = map.get(id);
  • 如果使用getBean获取多次相同的id的对象,那么默认是同一个对象
  • 如果在核心配置文件中编写多个相同类型(class)的bean,id不同时,获取的对象也不相同
  • id值一定是惟一的。如果不是唯一的就会抛出异常

4.4 spring创建对象的方式

4.4.1 默认方式

spring中默认是调用无参构造来创建对象的。

Class clz = Class.forName("类的全路径名");
clz.newInstance(); //无参构造

4.4.2 静态工厂模式

创建工厂类

// 静态工厂模式
public class CalendarStaticFactory {

    public static Calendar getCalendar(){
        return Calendar.getInstance();
    }
}

配置

<!--    通过静态工厂获取对象  factory-method 传入的是工厂方法-->
    <bean id="calendarStaticFactory" class="com.shangma.factory.CalendarStaticFactory" factory-method="getCalendar"></bean>

4.4.3 实例工厂模式

创建工厂类

// 实例工厂模式
public class CalendarObjectFactory {

    public Calendar getCalendar(){
        return Calendar.getInstance();
    }
}

配置

<!--   通过实例工厂获取对象-->
    <bean id="calendarObjectFactory" class="com.shangma.factory.CalendarObjectFactory"></bean>
<!--    调用实例工厂方法-->
    <bean id="calendar" factory-bean="calendarObjectFactory" factory-method="getCalendar"></bean>

4.4.4 spring内置工厂

创建工厂类,实现接口

public class CalendarSpringFactory implements FactoryBean<Calendar> {

    /**
     * 使用内置工厂模式生产的对象
     * @return
     * @throws Exception
     */
    @Override
    public Calendar getObject() throws Exception {
        return Calendar.getInstance();
    }

    /**
     * 获取生产对象的类型
     * @return
     */
    @Override
    public Class<?> getObjectType() {
        return Calendar.class;
    }

    /**
     * 产生的对象是否是单例模式
     * @return  false 不是单例模式
     * jdk8之后这个方法已经被实现了,返回值就是true,所以默认就是单例模式
     */
//    @Override
//    public boolean isSingleton() {
//        return false;
//    }
}

配置

<!--    通过spring内置工厂创建对象-->
    <bean id="calendarSpringFactory" class="com.shangma.factory.CalendarSpringFactory"></bean>

4.5 设置单例和多例

如果在单例模式下,当加载spring的核心配置文件时,就是创建对应bean标签的对象放入spring容器中。对象是被spring容器管理,所以什么时候销毁取决于容器的销毁。

在多例模式下,spring容器启动时解析xml发现bean标签后,只是将该bean进行管理,并不会创建对象。此后每次使用getbean()方法获取对象时,spring都会重新创建该对象返回。每次返回的都是一个新的对象。这个对象spring容器并不会持有。什么时候销毁取决于程序员什么时候销毁对象。

设置

        scope:设置单例 singleton  (默认)
              设置多例 prototype

    <bean class="com.shangma.entity.Person" id="person" scope="prototype"></bean>

4.6 懒加载机制

设置

<bean class="com.shangma.entity.Person" id="person" lazy-init="true"></bean>

懒加载机制设置后,当加载spring的核心配置文件时就不会创建对象,当第一次要真正获取这个对象时才去创建。

懒加载都是单例,多例下的懒加载没有意义。

4.7 初始化和销毁的方法

<!--    配置ProductDao对象
    init-method:配置初始化的方法
    destroy-method:配置销毁的方法
-->
    <bean id="productDao" class="com.shangma.dao.ProductDao" init-method="init" destroy-method="destory"></bean>

注意:需要验证销毁方法时,把spring容器关闭

5 Spring控制反转之依赖注入

5.1 什么是依赖注入?

控制反转(IOC),简单来说就是将对象的创建和生命周期管理的权利交由spring框架来处理。从此在开发中不需要在关注这些细节,而是在需要获取对象时直接从spring容器中获取就可以,这样的机制称之为控制反转。好处就是可以解耦。而在创建对象的过程中spring可以依赖配置对对象的属性进行设定值,这个过程称之为依赖注入(DI)。

依赖注入分为两种方式: set方法注入 、有参构造注入

5.2 set方法注入

set方法注入必须提供set方法

5.2.1 JDK内置注入的类型

创建类,添加属性

package com.shangma.entity;

import java.util.*;

/**
 * @author: ShangMa
 * @className: Hero
 * @description:
 * @date: 2022/8/3 16:30
 */
// 英雄
public class Hero {
    // 基本数据类型
    // id
    private int id;

    // 英雄名
    private String name;

    // 数组
    private int[] array;

    // 集合
    private List<String> list;

    // set
    private Set<String> set;

    // map
    private Map<String,String> map;

    // properties
    private Properties properties;

    public int getId() {
        return id;
    }

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

    public String getName() {
        return name;
    }

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

    public int[] getArray() {
        return array;
    }

    public void setArray(int[] array) {
        this.array = array;
    }

    public List<String> getList() {
        return list;
    }

    public void setList(List<String> list) {
        this.list = list;
    }

    public Set<String> getSet() {
        return set;
    }

    public void setSet(Set<String> set) {
        this.set = set;
    }

    public Map<String, String> getMap() {
        return map;
    }

    public void setMap(Map<String, String> map) {
        this.map = map;
    }

    public Properties getProperties() {
        return properties;
    }

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

    @Override
    public String toString() {
        return "Hero{" +
                "id=" + id +
                ", name='" + name + '\'' +
                ", array=" + Arrays.toString(array) +
                ", list=" + list +
                ", set=" + set +
                ", map=" + map +
                ", properties=" + properties +
                '}';
    }
}

在applicationContext.xml中配置

<bean id="hero" class="com.shangma.entity.Hero">
        <property name="id" value="10"></property>
        <property name="name" value="祖国人"></property>
        <property name="array">
            <array>
                <value>1</value>
                <value>2</value>
                <value>3</value>
            </array>
        </property>
        <property name="list">
            <list>
                <value>远程</value>
                <value>近战</value>
                <value>嘴遁</value>
            </list>
        </property>
        <property name="set">
            <set>
                <value>恶心</value>
                <value>变态</value>
                <value>虚伪</value>
            </set>
        </property>
        <property name="map">
            <map>
                <entry key="杨过" value="小龙女"></entry>
                <entry key="郭靖" value="黄蓉"></entry>
                <entry key="乔峰" value="阿朱"></entry>
            </map>
        </property>
        <property name="properties">
            <props>
                <prop key="大古">丽娜</prop>
                <prop key="虹猫">蓝兔</prop>
                <prop key="蓝猫">淘气</prop>
            </props>
        </property>
    </bean>

测试


        ClassPathXmlApplicationContext applicationContext = new 


        Hero hero = (Hero) applicationContext.getBean("hero");
        System.out.println(hero);

5.2.2 自定义类型的注入

自定义类型Dog和Cat

public class Dog {

    private int age;

    private String name;

    public int getAge() {
        return age;
    }

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

    public String getName() {
        return name;
    }

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

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



public class Cat {

    private int age;

    private String name;

    public int getAge() {
        return age;
    }

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

    public String getName() {
        return name;
    }

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


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

在spring容器中加入对象

<!--    创建狗和猫对象-->
    <bean id="dog" class="com.shangma.entity.Dog">
        <property name="age" value="10"></property>
        <property name="name" value="旺财"></property>
    </bean>
    <bean id="cat" class="com.shangma.entity.Cat">
        <property name="age" value="4"></property>
        <property name="name" value="进宝"></property>
    </bean>

依赖注入

5.2.3 自动装配

自定义类型时可以使用autowire进行自动装配

<bean id="hero" class="com.shangma.entity.Hero" autowire="byName">

byName: 根据属性名去spring容器中寻找相同id的对象,如果找到就会自动匹配上
byType: 根据属性的类型去spring容器中寻找相同class类型的对象,如果找到就会自动匹配上 如果在spring容器中有多个相同的类型,会抛出异常

也可以设置全局自动装配

当全局自动装配和局部自动装配同时存在时,采用就近原则

5.2.4 p命名空间赋值(了解)

设置p空间

在配置文件中赋值

<bean id="teacher" class="com.shangma.entity.Teacher" p:age="18" p:name="王晶" p:dog-ref="dog"></bean>

5.3 构造方法注入

创建类,不提供set方法,提供全参的构造方法

public class Student {

    private int age;

    private String name;

    private Dog dog;

    public Student(int age, String name, Dog dog) {
        this.age = age;
        this.name = name;
        this.dog = dog;
    }

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

在核心配置文件中注入

<!--
   构造方法注入
   index: 表示构造方法参数的索引  从0开始
   value:构造方法参数的值
   name:构造方法参数的参数名   name和index可以只设置一个,也可以全部都设置 但是如果全部设置必须要匹配上
   type:构造方法参数的类型  可以省略
   ref:表示引用spring容器中的对象的id
-->
    <bean id="student" class="com.shangma.entity.Student">
        <constructor-arg index="0" value="12"></constructor-arg>
        <constructor-arg name="name" value="二狗"></constructor-arg>
        <constructor-arg index="2" ref="dog"></constructor-arg>
    </bean>

6 注解方式实现IOC

6.1 注解使用IOC

需要导入spring-aop包(已经在spring-context包中包含)

  • 开启包扫描
<!--    开启包扫描-->
    <context:component-scan base-package="com.shangma"></context:component-scan>
  • 在类上添加注解
@Component  
public class Person {
	
}
  • 测试
        ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml");
        Person person = (Person) applicationContext.getBean("person");
        System.out.println(person);

默认会将类名的首字母变成小写后的值当作id值
如果类名的第二个字母是大写,那么以类名作为id值

也可以指定id值

@Component("abc")

延伸注解

@Component有三个延伸注解 功能一样,只是作用在不同的层中
@Controller 作用在web层
@Service service层
@Repository dao层

6.2 注解使用DI

@Value可以不依赖于set和构造方法注入值

@Autowired 自动装配

package com.shangma.entity;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.PropertySource;
import org.springframework.stereotype.Component;

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

/**
 * @author: ShangMa
 * @className: Student
 * @description:
 * @date: 2022/8/4 10:14
 */
@Component
@PropertySource("classpath:studentdata.properties")  //相当于 <context:property-placeholder location="studentdata.properties"></context:property-placeholder>
public class Student {
    // @Value 不依赖于set和构造方法
    @Value("${name}")
    private String name;
    @Value("${age}")
    private int age;
    @Value("#{@list}")
    private List<String> list;
    @Value("#{@set}")
    private Set<String> set;
    @Value("#{@map}")
    private Map<String,String> map;
    @Value("#{@properties}")
    private Properties properties;

    // @Autowired  如果自动装配的是一个类,那么先根据这个类的类型去spring容器中寻找相同类型的bean。
    // 如果找到匹配上。如果没有找到,再根据属性名去spring容器中寻找相同id的bean。
    // 如果找到就匹配上。如果找不到就抛出异常   先byType再byName

    // @Qualifier("dogxx") 可以指定要去spring容器中匹配的id
    @Autowired
//    @Qualifier("dog")
//    @Resource(name = "dog") //是javax.annotation-api中的注解     也可以指定要匹配的id  先byName再byType
    private Dog dog;
    // @Autowired 也可以作用在接口上面。先根据接口的类型去spring容器中寻找相同类型的bean
    // 如果找到就匹配上,如果没有找到,再根据属性名去spring容器中寻找相同id的bean。
    // 如果找到就匹配上。如果找不到去spring容器中寻找这个接口的实现类,如果找到一个,匹配。如果找到多个或者没有找到就抛异常
    @Autowired
//    @Resource
    private Cat cat;

    @Override
    public String toString() {
        return "Student{" +
                "name='" + name + '\'' +
                ", age=" + age +
                ", list=" + list +
                ", set=" + set +
                ", map=" + map +
                ", properties=" + properties +
                ", dog=" + dog +
                ", cat=" + cat +
                '}';
    }
}

7 代理设计模式

7.1 介绍

代理设计模式分为静态代理和动态代理。

静态代理需要手动创建一个代理类,增强被代理类的功能。

动态代理是根据被代理类动态的生成代理类。

7.2 静态代理

使用步骤:

  • 定义一个接口
  • 被代理者实现接口,实现方法
  • 代理者实现接口,增强方法
  • 代理者调用方法
package com.shangma.proxydemo;

/**
 * @author: ShangMa
 * @className: StaticProxyDemo
 * @description:
 * @date: 2022/8/4 15:40
 */
public class StaticProxyDemo {

    public static void main(String[] args) {
        JingjiR jingjiR = new JingjiR();
        jingjiR.sing();
        jingjiR.dance();
        jingjiR.rap();
    }
}



interface Skill{
    void sing();
    void dance();
    void rap();
}

// 被代理者  --明星
class Mingxing implements Skill{

    @Override
    public void sing() {
        System.out.println("只因你太美~~~~");
    }

    @Override
    public void dance() {
        System.out.println("Are you ready to dance ~");
    }

    @Override
    public void rap() {
        System.out.println("老子没得文化,老子啥子都不怕,老子来手机电话号码3个录3个8");
    }
}

// 代理者  --经纪人
class JingjiR implements Skill{

    private Mingxing mingxing = new Mingxing();

    @Override
    public void sing() {
        System.out.println("你是谁?");
        System.out.println("一场多少钱?");
        mingxing.sing();
        System.out.println("理财");
    }

    @Override
    public void dance() {
        System.out.println("你是谁?");
        System.out.println("一场多少钱?");
        mingxing.dance();
        System.out.println("理财");
    }

    @Override
    public void rap() {
        System.out.println("你是谁?");
        System.out.println("一场多少钱?");
        mingxing.rap();
        System.out.println("理财");
    }
}

静态代理优点:容易理解,能够把业务逻辑和功能性代码分开.

缺点:有大量重复的代码

7.3 动态代理

可以根据被代理者类动态的生成代理者类。

动态代理分开JDK自带动态代理和CGLIB动态代理。

7.3.1 jdk自带动态代理

JDK自带的动态代理需要被代理者实现接口

package com.shangma.proxydemo2;

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

/**
 * @author: ShangMa
 * @className: DynycProxyDemo
 * @description:
 * @date: 2022/8/4 16:25
 */
public class DynycProxyDemo {
    public static void main(String[] args) {
        // 创建被代理者
        Fanfan fanfan = new Fanfan();
        // 动态生成代理者
        /**
         * 第一个参数:被代理者的类加载器
         * 第二个参数:被代理者的接口
         * 第三个参数:回调函数
         * 返回值:动态生成的代理者
         */
        Skill proxyInstance = (Skill) Proxy.newProxyInstance(fanfan.getClass().getClassLoader(), fanfan.getClass().getInterfaces(), new InvocationHandler() {

            /**
             *   当代理者调用方法时会被下面的方法拦截
             * @param proxy     代理者
             * @param method    需要执行的方法
             * @param args      执行方法的参数
             * @return 执行方法后的返回值
             */
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                if ("sing".equals(method.getName())){
                    System.out.println("你是谁?");
                    System.out.println("多少钱?");
                }

                Object object = method.invoke(fanfan, args);
                return object;
            }
        });
        proxyInstance.sing(10);
        proxyInstance.dance();
    }
}


interface Skill {
    void sing(int i);

    void dance();
}


// 被代理者
class Fanfan implements Skill {

    @Override
    public void sing(int i) {
        System.out.println("这个碗,又大又圆。这个面,又长又宽");
    }

    @Override
    public void dance() {
        System.out.println("在酒吧甩脑壳~");
    }
}

7.3.2 CGLIB动态代理

第三方提供的一种动态生成代理的技术。基于继承实现的。

根据被代理者类来生成它的子类作为代理者

public class TestDemo {
    public static void main(String[] args) {
        // 创建被代理者对象
        Gazi gazi = new Gazi();

        Enhancer enhancer = new Enhancer();
        // 设置类加载器
        enhancer.setClassLoader(gazi.getClass().getClassLoader());
        // 设置父类
        enhancer.setSuperclass(gazi.getClass());
        // 设置回调函数
        enhancer.setCallback(new MethodInterceptor() {
            // 当代理者调用方法时会被下面的方法拦截

            /**
             *
             * @param o        代理者
             * @param method   执行的方法
             * @param objects  执行方法的参数
             * @param methodProxy  方法代理
             * @return     执行方法的返回值
             */
            @Override
            public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
                System.out.println("你是谁?");
                System.out.println("干嘛的?");
                Object invoke = method.invoke(gazi, objects);
                return invoke;
            }
        });

        // 创建代理者对象
        Gazi gazi1 = (Gazi) enhancer.create();
        gazi1.stealDog();
    }
}


// 被代理者
class Gazi{
    public void stealDog(){
        System.out.println("嘎子偷狗......");
    }
}

8 SpringAOP

8.1 AOP概念

AOP称之为面向切面编程。在层于层之间增加一个可编程的切面,用来将功能性代码和业务逻辑代码进行统一处理,有助于实现高内聚特性。

8.2 AOP的作用

  • 记录日志
  • 性能统计
  • 权限控制
  • 事务处理
  • 异常处理

8.3 AOP的好处

  • 高内聚
  • 更好的代码复用性
  • 增强扩展性

8.4 AOP的实现原理

  • jdk动态代理
    • 基于接口的动态代理
    • 如果被代理对象实现了接口,那么使用jdk动态代理
  • cglib动态代理
    • 基于继承的动态代理
    • 如果被代理对象没有实现接口,那么使用cglib动态代理

8.5 AOP的专业术语

  • 连接点
    • 层于层之间调用过程中,调用目标层中方法的过程,称之为连接点
  • 切入点表达式
    • 筛选哪些连接点需要增强的表达式。分为粗粒度和细粒度。
  • 切入点
    • 在连接点的基础上,根据切入点表达式筛选出来的连接点称之为切入点。
  • 切面
    • 当spring拦截下切入点后,将这些切入点交给处理进行功能的增强,这个处理类就称之为切面
  • 通知
    • 切面类中提供的一些可以在目标方法执行前后执行的方法,称之为通知,也叫增强方法
  • 目标对象
    • 真正希望访问到的对象。 (比如web层方法调service方法,那个具体的service实现类就是目标对象)
  • 织入(了解)
    • 把某些增强功能写入到切面的通知中。(比如把事务放到切面里)--2023年其他博客解释(切入点被哪些通知方法增强,是前置增强还是后置增强)

8.6 快速入门

8.7 切入点表达式

切入点表达式分为粗粒度细粒度

8.7.1 粗粒度

通过类名进行匹配。

格式:within(包名.类名)

  • 表示在指定包下指定类下的所有的连接点都会被表达式识别,变成切入点
within(com.shangma.service.impl.UserServiceImpl)
  • 在impl包下的所有的类中的方法都会被表达式识别,变成切入点
within(com.shangma.service.impl.*)
  • 匹配impl包下所有一级目录下的所有的类(只能是一级目录)
within(com.shangma.service.impl.*.*)
  • 匹配impl包下所有子孙包中的所有的类
within(com.shangma.service.impl..*)

8.7.2 细粒度

细粒度的切入点表达式,可以以方法为单位定义切入点规则。

格式:execution(返回值类型 包名.类名.方法名(参数类型,参数类型))

  • 匹配UserServiceImpl类下的addUser方法,并且参数类型只有一个,是com.shangma.entity.User类型。返回值类型必须也是User类型
execution(com.shangma.entity.User com.shangma.service.impl.UserServiceImpl.addUser(com.shangma.entity.User))
  • 匹配impl包下所有类的find方法,要求这个方法没有参数,没有返回值。
execution(void com.shangma.service.impl.*.find())
  • 匹配impl包下所有类的findUser方法,这个方法参数不限,没有返回值。
execution(void com.shangma.service.impl.*.findUser(..))
  • 匹配impl包下所有类的findUser方法,这个方法参数不限,返回值类型不限。
execution(* com.shangma.service.impl.*.findUser(..))
  • 匹配impl包下的所有子孙包下的所有的类的所有的方法(参数类型不限,返回值不限)
execution(* com.shangma.service.impl..*.*(..))
  • 匹配impl包下的所有子孙包下的所有的类的所有的find开头的方法(参数类型不限,返回值不限)
execution(* com.shangma.service.impl..*.find*(..))

8.8 5大通知类型

通知中除了环绕通知都可以接收JoinPoint参数,如果有多个参数,那么JoinPoint参数必须是列表中的第一个

  • 前置通知
    • 在目标方法执行之前执行的通知
  • 后置通知
    • 在目标方法成功执行完成后执行的通知
  • 环绕通知
    • 必须显式的调用目标方法,如果不调用,目标方法就不会执行
    • 环绕通知是在目标方法执行之前和之后都能够执行的通知
    • 环绕通知可以接收一个ProceedingJoinPoint参数,可以调用目标方法
    • 环绕通知必须返回返回值,否则web层就获取不到返回值
  • 异常通知
    • 目标方法出现异常时调用的通知
  • 最终通知
    • 无论目标方法执行是否成功,都会在目标方法后执行的通知
package com.shangma.aspects;

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.stereotype.Component;

import java.lang.reflect.Method;

/**
 * @author: ShangMa
 * @className: FirstAspect
 * @description:
 * @date: 2022/8/5 10:26
 */
@Component
public class FirstAspect {

    public void before(JoinPoint joinPoint){
        // 获取目标对象
        Object target = joinPoint.getTarget();
        System.out.println(target.getClass());

        // 获取方法签名信息
        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
        // 获取目标方法
        Method method = signature.getMethod();
        System.out.println(method.getName());

        // 获取返回值类型
        Class returnType = signature.getReturnType();
        // 获取异常类型
        Class[] exceptionTypes = signature.getExceptionTypes();
        // 获取参数名称
        String[] parameterNames = signature.getParameterNames();
        // 获取参数类型
        Class[] parameterTypes = signature.getParameterTypes();
        System.out.println("before方法执行了...");
    }


    public Object around(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
        System.out.println("环绕通知执行前...");

        // 显式调用目标方法
         Object proceed = proceedingJoinPoint.proceed();
        

        System.out.println("环绕通知执行后...");
        return proceed;
    }


    // 后置通知  目标方法是一定执行完毕了
    public void afterReturning(JoinPoint joinPoint,Object msg){
        System.out.println("后置通知执行了..." + msg);
    }

    // 异常通知  目标方法出现异常时的通知
    public void afterThrowing(JoinPoint joinPoint,Throwable e){
        System.out.println("异常通知执行了..." + e.getMessage());
    }

    // 最终通知  无论目标方法是否执行完毕都会执行的通知
    public void after(JoinPoint joinPoint){
        System.out.println("最终通知执行了...");
    }
}

xml配置

<!--        配置切面 配置通知-->
        <aop:aspect ref="firstAspect">
<!--            配置前置通知-->
<!--            <aop:before method="before" pointcut-ref="pc01"></aop:before>-->
<!--            配置环绕通知-->
            <aop:around method="around" pointcut-ref="pc01"></aop:around>
<!--            配置后置通知-->
<!--            <aop:after-returning method="afterReturning" pointcut-ref="pc01" returning="msg"></aop:after-returning>-->
<!--            配置异常通知-->
            <aop:after-throwing method="afterThrowing" pointcut-ref="pc01" throwing="e"></aop:after-throwing>
<!--            配置最终通知-->
<!--            <aop:after method="after" pointcut-ref="pc01"></aop:after>-->
        </aop:aspect>

注意:(xml中无论怎么配置)前置肯定比后置先执行, 前置肯定比还绕后先执行。
所以,前置跟环绕通知以及最终跟后置这两对相互之间的执行顺序是跟他们配置的顺序相关

9 AOP案例

  • 统计业务层中方法执行的时间
  • 使用AOP实现权限控制
    • 假设有三个权限(游客cust 用户user 管理员admin)
    • 游客可以注册用户
    • 用户可以查询和修改和注册用户
    • 管理员额外可以删除用户

见practice-aop案例



SpringMVC

1 什么是MVC?

MVC是23种常见设计模式之一。全称是Model View Controller

Model:数据模型 就是指数据 JavaBean

View:视图 就是我们用的html jsp等来展示界面的

Controller:控制器 主要是控制model和view以及用户交互部分 servlet

2 SpringMVC的概述

Spring Web MVC是基于servlet API构建的原始web框架。从一开始就已经包含在了Spring框架中。通常成为springmvc.

优点:

  • 对Serlvet API进行了高级的封装,使API更简单。
  • 避免繁琐的获取表单参数。
  • 避免多余的servlet的使用。

3 SpringMVC的执行流程

4 SpringMVC的内部流程

4.1 SpringMVC中的组件

  • 前端控制器
  • 处理器映射器
  • 处理器适配器
  • 前端解析器
  • 处理器

下边的图稍后会换成狂神说的ssm图

5 SpringMVC入门案例

导入依赖

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

创建页面

创建控制器

public class MyController implements Controller {

    @Override
    public ModelAndView handleRequest(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse) throws Exception {
        ModelAndView modelAndView = new ModelAndView();
        // 数据
        modelAndView.addObject("msg","hello Springmvc");
        // 页面
        modelAndView.setViewName("success");
        return modelAndView;
    }
}

创建springmvc.xml

<!--    开启包扫描-->
    <context:component-scan base-package="com.shangma"></context:component-scan>
<!--    开启mvc驱动-->
    <mvc:annotation-driven/>
<!--    配置前端解析器-->
    <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
        <property name="prefix" value="/WEB-INF/pages/"></property>
        <property name="suffix" value=".jsp"></property>
    </bean>

在web.xml中配置前端控制器

<!--    配置前端控制器(本质上就是一个servlet)-->
    <servlet>
        <servlet-name>dispatcherServlet</servlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<!--        配置初始化参数-->
        <init-param>
            <param-name>contextConfigLocation</param-name>
            <param-value>classpath:springmvc.xml</param-value>
        </init-param>
<!--        优先级-->
        <load-on-startup>1</load-on-startup>
    </servlet>

    <servlet-mapping>
        <servlet-name>dispatcherServlet</servlet-name>
        <url-pattern>/</url-pattern>
    </servlet-mapping>

配置tomcat,启动

6 @RequestMapping


      value:请求的路径
      method:请求的方式  一般用get post put delete
      headers:请求头必须包含哪些内容
     
      @RequestMapping的延伸注解
      @GetMapping     只能接收get请求
      @PostMapping    只能接收post请求
      @PutMapping     只能接收put请求
      @DeleteMapping  只能接收delete请求

7 接收参数

只要前端属性名和后端参数名或属性名相同即可接收成功

 @GetMapping("fun2")
    public String fun2(String[] hobby){
        System.out.println(Arrays.toString(hobby));
        return "success";
    }

    // 只要前端参数名和后端实体类的属性名相同,那么就可以自动封装成对象
    @GetMapping("fun3")
    public String fun3(User user){
        System.out.println(user);
        return "success";
    }

    @GetMapping("fun4")
    public String fun4(MyUser myUser){
        System.out.println(myUser);
        return "success";
    }

    @GetMapping("fun5")
    public String fun5(MyUser myUser){
//        System.out.println();
        return "success";
    }

    @GetMapping("fun6")
    public String fun6(MyUser myUser){
        System.out.println(myUser);
        return "success";
    }

    // json数据用于post和put请求
    // fastjson  jackson  gson  sbjson
    // jackson可以和springmvc无缝整合
    @PostMapping("fun7")
    public String fun7(@RequestBody User user) {
        System.out.println(user);
        return "success";
    }

前端内容

<h2>普通参数类型</h2>
<form action="/fun1" method="post">
    用户名: <input type="text" name="username"> <br>
    密码: <input type="text" name="password"> <br>
    <input type="submit" value="提交">
</form>

<h2>数组类型</h2>
<form action="/fun2">
    <input type="checkbox" name="hobby" value="篮球">篮球 <br>
    <input type="checkbox" name="hobby" value="唱歌">唱歌 <br>
    <input type="checkbox" name="hobby" value="跳舞">跳舞 <br>
    <input type="checkbox" name="hobby" value="rap">嘻哈 <br>
    <input type="submit" value="提交">
</form>

<h2>封装对象</h2>
<form action="/fun3">
    <input type="text" name="id"> <br>
    <input type="text" name="username"> <br>
    <input type="text" name="sex"> <br>
    <input type="submit" value="提交">
</form>

<h3>包装类型</h3>
<form action="/fun4">
    <input type="text" name="myUserName"> <br>
    <input type="text" name="user.id"> <br>
    <input type="text" name="user.username"> <br>
    <input type="text" name="user.sex"> <br>
    <input type="submit" value="提交">
</form>

<h4>集合类型</h4>
<form action="/fun5">
    <input type="text" name="list[0].id"> <br>
    <input type="text" name="list[0].username"> <br>
    <input type="text" name="list[0].sex"> <br>
    <input type="text" name="list[1].id"> <br>
    <input type="text" name="list[1].username"> <br>
    <input type="text" name="list[1].sex"> <br>
    <input type="text" name="list[2].id"> <br>
    <input type="text" name="list[2].username"> <br>
    <input type="text" name="list[2].sex"> <br>
    <input type="submit" value="提交">
</form>

<h5>Map类型</h5>
<form action="/fun6">
    <input type="text" name="map['aaa']"> <br>
    <input type="text" name="map['bbb']"> <br>
    <input type="text" name="map['ccc']"> <br>
    <input type="submit" value="提交">
</form>

8 post中文乱码处理

<!--    post请求中文乱码问题-->
    <filter>
        <filter-name>characterEncodingFilter</filter-name>
        <filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
        <init-param>
            <param-name>encoding</param-name>
            <param-value>utf-8</param-value>
        </init-param>
    </filter>
    <filter-mapping>
        <filter-name>characterEncodingFilter</filter-name>
        <url-pattern>/*</url-pattern>
    </filter-mapping>

9 返回值类型

/**
     * 返回ModelAndView 携带了数据和页面
     * jsp页面可以用el表达式获取数据
     */
    @GetMapping("test01")
    public ModelAndView test01(){
        ModelAndView modelAndView = new ModelAndView();
        modelAndView.addObject("msg","test....");
        modelAndView.setViewName("success");
        return modelAndView;
    }

    /**
     * 如果返回的是字符串,默认是根据视图解析器中的前缀和后缀拼接jsp页面
     *
     * @ResponseBody : 会返回前端一个普通字符串
     * produces = "text/html;charset=utf-8" 响应中文乱码解决
     */
    @GetMapping(value = "test02",produces = "text/html;charset=utf-8")
    @ResponseBody
    public String test02(){
        return "我是大傻瓜";
    }

    // 转发
    @GetMapping("forward")
    public String test03(){
//        request.getRequestDispatcher("/test02").forward(request, response);
//        response.sendRedirect("");
        return "forward:test02";
    }

    // 重定向
    @GetMapping("redirect")
    public String test04(){
//        request.getRequestDispatcher("/test02").forward(request, response);
//        response.sendRedirect("");
        return "redirect:http://www.jd.com";
    }

    // 如果返回的是一个对象,那么默认去@GetMapping的路径中寻找jsp页面
    @GetMapping("user")
    @ResponseBody // 把对象转换成json字符串返回给前端
    public User test05(){
        User user = new User();
        user.setId(10);
        user.setUsername("王五");
        user.setSex("女");
        return user;
    }

    // 返回void会去找路径名称的jsp页面
    // 一般可以使用转发和重定向
    @GetMapping("void")
    public void test06(){

    }

    // ResponseEntity
    @GetMapping("returnEntity")
    // ResponseEntity默认返回的就是json字符串
    public ResponseEntity returnEntity(){
        User user = new User();
        user.setUsername("王五");
        user.setSex("男");
        user.setId(19);
        return ResponseEntity.ok(user);
    }

10 Rest风格

是一种接口设计规范。

传统风格:

localhost:8080/queryAll 查询所有

localhost:8080/queryById?id=1 根据id查询

localhost:8080/addUser 添加用户

localhost:8080/update 更新用户

localhost:8080/deleteUserById?id=1 删除用户

rest风格:

get localhost:8080/user 查询所有用户

get localhost:8080/user/1 根据id查询用户

post localhost:8080/user 添加用户

put localhost:8080/user 更新用户

delete localhost:8080/user/1 根据id删除用户

  • get请求一般查询数据
  • post请求一般是添加数据,登录
  • put请求一般是更新用户
  • delete请求一般是删除数据
package com.shangma.controller;

import com.shangma.entity.User;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.*;

import java.util.List;

/**
 * @author: ShangMa
 * @className: TestMyController
 * @description:
 * @date: 2022/8/6 17:27
 */
@Controller
@RequestMapping("users")
public class UserController {

    @GetMapping
    public List<User> findAll(){
        // 调用service层的查询所有方法
        System.out.println("查询所有...");
        return null;
    }

    @GetMapping("/{id}")
    public void findById(@PathVariable Integer id){
        System.out.println("根据id查询" + id );
//        return new User();
    }

    @PostMapping
    public void addUser(@RequestBody User user){
        System.out.println("添加用户" + user);
    }

    @PutMapping
    public void updateUser(@RequestBody User user){
        System.out.println("更新用户" + user);
    }

    @DeleteMapping("1")
    public void deleteUser(Integer id){
        System.out.println("删除用户" + id);
    }
}

11 文件上传

文件上传三要素:

  • post请求
  • enctype="multipart/form-data"
  • input框设置成file

11.1 文件上传之part方式

不需要额外导包

第一步,配置

        <multipart-config>
<!--            单位是字节-->
<!--            <max-file-size></max-file-size>-->
<!--            <max-request-size></max-request-size>-->
        </multipart-config>

第二步:后端获取Part对象

@PostMapping("upload")
    public String upload(HttpServletRequest request) throws IOException, ServletException {

        // 获取文件流
        Part part = request.getPart("img");
        String realPath = request.getServletContext().getRealPath("/WEB-INF/");
        part.write(realPath + part.getSubmittedFileName());
        return "success";
    }

第三部:前端编写

<form action="/upload" method="post" enctype="multipart/form-data">
    <input type="file" name="img"> <br>
    <input type="submit"value="提交">
</form>

多文件上传无非就是获取一个part对象的集合

@PostMapping("multiupload")
    public String upload2(HttpServletRequest request) throws IOException, ServletException {
        Collection<Part> parts = request.getParts();
        String realPath = request.getServletContext().getRealPath("/WEB-INF/");
        parts.forEach(part -> {
            try {
                part.write(realPath + part.getSubmittedFileName());
            } catch (IOException e) {
                e.printStackTrace();
            }
        });
        return "success";
    }

11.2 文件上传之springmvc方式

导包

<dependency>
            <groupId>commons-fileupload</groupId>
            <artifactId>commons-fileupload</artifactId>
            <version>1.2.2</version>
        </dependency>
        <dependency>
            <groupId>commons-io</groupId>
            <artifactId>commons-io</artifactId>
            <version>1.4</version>
        </dependency>

直接获取上传的对象

// 这里的参数名称必须要前端的表单参数名称相同
    @PostMapping("hello")
    public String hello(MultipartFile fx) throws IOException {
        // 上传的文件名
//        fx.getOriginalFilename()
        FileUtils.writeByteArrayToFile(new File("d:\\资源\\" + fx.getOriginalFilename()),fx.getBytes());
        return "success";
    }

12 文件下载

12.1 超链接方式

这种方式如果浏览器支持的格式,那么会直接打开,不会再去自动下载。需要用户使用保存去下载。

<a href="/download/aaaaa.txt">下载txt</a>
<a href="/download/finance_user_menu.sql">下载sql</a>
<a href="/download/heeeee.doc">下载doc</a>
<a href="/download/meinv.webp">下载meinv</a>
<a href="/download/小美女.webp">下载小美女</a>
<a href="/download/桌面共享.exe">下载exe</a>
<a href="/download/谷丰硕--第三阶段课程进度.xlsx">下载xlsx</a>
<a href="/download/项目演示.md">下载md</a>

12.2 编码方式

可以下载任意格式的文件

@GetMapping("downloadImg")
    public ResponseEntity<byte[]> fun1(HttpServletRequest request) throws IOException {

        // 把服务器的文件读取出来,然后传递给前端。并且告诉前端不要打开,要去下载。
        String realPath = request.getServletContext().getRealPath("/download/小美女.webp");
        // 读取服务器中的文件到字节数组中
        FileInputStream in = new FileInputStream(realPath);
        byte[] buffer = new byte[in.available()];
        in.read(buffer);

        // 获取文件名
        String fileName = realPath.substring(realPath.lastIndexOf("\\") + 1);
        // 设置响应头来让前端去下载
        // URLEncoder.encode(fileName,"utf-8") 解决浏览器中文乱码
        HttpHeaders headers = new HttpHeaders();
        headers.setContentDispositionFormData("attachment", URLEncoder.encode(fileName,"utf-8"));
        ResponseEntity<byte[]> entity = new ResponseEntity<byte[]>(buffer,headers, HttpStatus.OK);
        return entity;
    }

13 静态资源处理

由于前端控制器使用的是/,会拦截除了jsp之外的任何请求。所以请求静态资源的内容也会被前端控制器拦截。导致静态资源不能访问

13.1 静态资源处理方式一

不要全部拦截,如果是公司的服务请求,使用xxxx.do 这样来区分静态资源和服务请求

<servlet-mapping>
        <servlet-name>dispatcherServlet</servlet-name>
        <url-pattern>*.do</url-pattern>
    </servlet-mapping>

会破坏rest风格。

13.2 静态资源处理方式二

<!--    静态资源放行 推荐-->
    <mvc:default-servlet-handler/>

13.3 静态资源处理方式三

    <mvc:resources mapping="pages/*" location="pages"></mvc:resources>
    <mvc:resources mapping="img/*" location="img"></mvc:resources>
    <mvc:resources mapping="css/*" location="css"></mvc:resources>
    <mvc:resources mapping="js/*" location="js"></mvc:resources>

14 异常处理

14.1 异常处理方式一

<error-page>
        <location>/WEB-INF/errors/err.jsp</location>
    </error-page>

14.2 异常处理方式二

异常捕获类

@Component
public class MyExceptionRes implements HandlerExceptionResolver {
    @Override
    public ModelAndView resolveException(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, Exception e) {
        ModelAndView modelAndView = new ModelAndView();
        modelAndView.addObject("msg",e.getMessage());
        modelAndView.setViewName("errors/err");
        return modelAndView;
    }
}

14.3 异常处理方法三 (必须掌握)

第一步:编写枚举

public enum StatusEnum {
    ERROR(50000,"操作失败"),
    USER_NOT_FOUND(20001,"用户名错误")
    ;

    private int status;

    private String message;

    StatusEnum(int status, String message) {
        this.status = status;
        this.message = message;
    }

    public int getStatus() {
        return status;
    }

    public String getMessage() {
        return message;
    }
}

第二步:自定义异常

public class MyException extends RuntimeException{

    private StatusEnum statusEnum;

    public MyException(StatusEnum statusEnum){
        this.statusEnum = statusEnum;
    }

    public StatusEnum getStatusEnum() {
        return statusEnum;
    }
}

第三步:编写包装类来返回前端

package com.shangma.beans;

/**
 * @author: ShangMa
 * @className: ExceptionMessageBean
 * @description:
 * @date: 2022/8/15 15:05
 */
public class ExceptionMessageBean {

    private int status;

    private String message;

    public int getStatus() {
        return status;
    }

    public ExceptionMessageBean() {
    }

    public ExceptionMessageBean(int status, String message) {
        this.status = status;
        this.message = message;
    }

    public void setStatus(int status) {
        this.status = status;
    }

    public String getMessage() {
        return message;
    }

    public void setMessage(String message) {
        this.message = message;
    }
}

第四步:捕获异常

@RestControllerAdvice
public class MyCustmerExceptionHandler {

    // 接收异常的方法
    @ExceptionHandler(MyException.class)
    public ResponseEntity handler(MyException myException){
        StatusEnum statusEnum = myException.getStatusEnum();
        ExceptionMessageBean bean = new ExceptionMessageBean(statusEnum.getStatus(),statusEnum.getMessage());
        return ResponseEntity.ok(bean);
    }
}

15 拦截器

过滤器是javaweb servlet当中学的 /*拦截所有的请求 包含静态资源 可以在任意web项目中使用

拦截器 只会拦截controller当中的各种mapping,属于springmvc的一个技术。

定义类实现接口

public class FirstInterceptor implements HandlerInterceptor {

    // 在controller中方法执行之前执行的方法
    // 返回值如果return ture 程序继续向下执行  false 程序不往下执行
    @Override   
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        System.out.println("first  preHandle....");
        return true;
    }


    // controller中方法执行之后执行的方法
    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
        System.out.println("first  postHandle....");
    }

    // 请求和响应都完成后执行
    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        System.out.println("first afterCompletion....");
    }
}

在springmvc.xml中进行配置

<!--    配置拦截器-->
    <mvc:interceptors>
        <mvc:interceptor>
<!--            需要拦截的请求-->
            <mvc:mapping path="/test/*"/>
            <mvc:mapping path="/error"/>
            <mvc:exclude-mapping path="/test/test2"/>
    <!--        配置拦截器对象-->
            <bean class="com.shangma.interceptor.FirstInterceptor"></bean>
        </mvc:interceptor>
        <mvc:interceptor>
            <!--            需要拦截的请求-->
            <mvc:mapping path="/test/*"/>
            <mvc:mapping path="/error"/>
            <mvc:exclude-mapping path="/test/test2"/>
            <!--        配置拦截器对象-->
            <bean class="com.shangma.interceptor.SecondInterceptor"></bean>
        </mvc:interceptor>
    </mvc:interceptors>

16 日期格式转换

16.1 key=value形式日期格式转换

16.1.1 局部变量转换

在属性上添加注解@DateTimeFormat(pattern="日期格式")

    // 局部日期格式转换
    @DateTimeFormat(pattern = "yyyy-MM-dd")
    private Date birthday;

    @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
    private LocalDateTime localDateTime;
    
    @DateTimeFormat(pattern = "yyyy-MM-dd")
    private LocalDate localDate;

16.1.2 全局变量转换

先定义类,实现接口

// 全局处理日期类型转换
public class MyConvert implements Converter<String, Date> {

    @Override
    public Date convert(String s) {
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
        Date parse = null;
        try {
            parse = sdf.parse(s);
        } catch (ParseException e) {
            SimpleDateFormat sdf1 = new SimpleDateFormat("yyyy/MM/dd");
            try {
                parse = sdf1.parse(s);
            } catch (ParseException parseException) {
                parseException.printStackTrace();
            }
        }
        return parse;
    }
}

在springmvc.xml中配置

<!--    配置转换器-->
    <bean id="conversionService2" class="org.springframework.format.support.FormattingConversionServiceFactoryBean">
        <property name="converters">
            <set>
                <bean class="com.shangma.convers.MyConvert"></bean>
            </set>
        </property>
    </bean>

16.2 json形式日期格式转换

    // 局部日期格式转换
//    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
    //Date json转换默认值是 yyyy-MM-dd
    private Date birthday;


    // @JsonFormat无论接收还是返回都会生效
    //localDateTime json转换默认值是 yyyy-MM-ddTHH:mm:ss
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
    private LocalDateTime localDateTime;

    @JsonFormat(pattern = "yyyy-MM-dd")
//LocalDate json转换默认值是 yyyy-MM-dd
    private LocalDate localDate;
 posted on 2022-08-11 16:29  luckynum11  阅读(23)  评论(0编辑  收藏  举报