SSM笔记

SSM

Mybatis(持久层框架)

Day 1

框架介绍

在这里插入图片描述
在这里插入图片描述

三层架构

在这里插入图片描述
在这里插入图片描述

持久层技术总结

在这里插入图片描述

概述

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

Mybatis入门

注意事项

在这里插入图片描述
使用Mybatis,Pom需要导入的坐标

<dependencies>
    <dependency>
        <groupId>org.mybatis</groupId>
        <artifactId>mybatis</artifactId>
        <version>3.4.5</version>
    </dependency>
<dependencies>

在resources目录下创建文件SqlMapConfig.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>
<!--    配置环境-->
    <environments default="mysql">
    <!--        配置mysql的环境-->
        <environment id="mysql">
    <!--            事务类型-->
            <transactionManager type="JDBC">
                
            </transactionManager>
    <!--            配置数据源(连接池)-->
            <dataSource type="POOLED">
                <!--配置连接数据库的4个基本信息-->
                <property name="driver" value="com.mysql.jdbc.Driver"/>
                <property name="url" value="jdbc:mysql://localhost:3306/mybatis"/>
                <property name="username" value="root"/>
                <property name="password" value="root"/>
            </dataSource>
        </environment>
    </environments>
<!--    指定映射配置文件的位置,映射配置文件指的是每个dao的独立的配置文件-->
    <mappers>
        <mapper resource="itheima/dao/IUserDao.xml"></mapper>
    </mappers>
</configuration>

在resources目录下创建itheima/dao的目录,创建IuserDao.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="itheima.dao.IUserDao">
<!--    配置查询所有--> 
    <select id="findAll" resultType="itheima.domain.User">
        select * from user
    </select>
</mapper>

映射配置文件的目录结构和dao接口的包结构一致的话,就无需再写接口的实现类,如IuserDao.xml
在这里插入图片描述

入门案例(使用xml)

在这里插入图片描述

//1.读取配置文件
InputStream is = Resources.getResourceAsStream("SqlMapConfig.xml");

//2.创建SqlSessionFactory工厂
SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
SqlSessionFactory factory = builder.build(is);
//3.使用工厂生产SqlSession对象
SqlSession session = factory.openSession();
//4.使用SqlSession创建Dao接口的代理对象
IUserDao userDao = session.getMapper(IUserDao.class);
//5.使用代理对象执行方法
List<User> list = userDao.findAll();
for (User user : list) {
    System.out.println(user);
}
//6.释放资源
session.close();
is.close();
使用注解方式替代xml

将IUserDao.xml文件删除,同时在Iuserdao接口的方法上加上注解
在这里插入图片描述

同时在SqlMapConfig.xml文件中,将映射改成指定为class

<mapper class="itheima.dao.IUserDao"></mapper>

可以为接口实现一个类
通过调用session对象的selectList方法
参数为:指定接口的方法

在这里插入图片描述
测试代码

//1.读取配置文件
InputStream is = Resources.getResourceAsStream("SqlMapConfig.xml");

//2.创建SqlSessionFactory工厂
SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
SqlSessionFactory factory = builder.build(is);
//3.使用工厂创建dao对象(session在实现类中已经被创建并调用过)
IUserDao userDao = new UserDaoImpl(factory);

//4.使用代理对象执行方法
List<User> list = userDao.findAll();
for (User user : list) {
    System.out.println(user);
}
//5.释放资源
is.close();

在这里插入图片描述

总结

Mybatis根据配置文件中的mapper标签对中的全限定类名(接口类名)
可以通过以配置文件为输入流创建的工厂对象创建的Sqlsession
以Sqlsession的对象来创建接口的代理对象
且可以使用注解或配置文件的方式来实现接口中的方法

  1. 如果使用配置文件配置:
    则在Mybatis的配置文件中的mapper指明接口对应映射的配置文件位置(resource=xx),如果创建的映射配置文件的目录结构和dao接口的包结构一致的话,就无需再写接口的实现类调用接口的方法,直接可以创建其代理对象。
  2. 如果使用注解配置:
    则在接口的方法上写明@Select(sql语句),且返回结果(类对象)与数据库中的关系型数据对应
    在Mybatis的配置文件中,mapper不再是resource的位置,而是class=”全限定类名”(itheima.dao.IUserDao)

设计模式分析

在这里插入图片描述
Mybatis在代理dao对象的时候做了什么事?

  1. 创建代理对象
  2. 在代理对象中调用方法的时候,调用了Sqlsession的selectList方法

在这里插入图片描述

  • 传递给SelectList 全限定类名+方法名 的时候,可以通过map获取其对应的mapper对象,里面有对应的sql语句以及类路径字符串
  • 代理对象实际上也是在内部实现了SelectList方法

Day 2

今天的学习内容

在这里插入图片描述
在这里插入图片描述

Mybatis环境搭建步骤

在这里插入图片描述
SqlMapConfig.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>
<!--    配置环境-->
    <environments default="mysql">
    <!--        配置mysql的环境-->
        <environment id="mysql">
    <!--            事务类型-->
            <transactionManager type="JDBC">
            </transactionManager>
    <!--            配置数据源(连接池)-->
            <dataSource type="POOLED">
                <!--配置连接数据库的4个基本信息-->
                <property name="driver" value="com.mysql.jdbc.Driver"/>
                <property name="url" value="jdbc:mysql://localhost:3306/mybatis"/>
                <property name="username" value="root"/>
                <property name="password" value="root"/>
            </dataSource>
        </environment>

    </environments>

<!--    指定映射配置文件的位置,映射配置文件指的是每个dao的独立的配置文件-->
    <mappers>
<!--        使用配置文件配置-->
        <!--<mapper resource="itheima/dao/IUserDao.xml"/>-->

        <!--    使用注解配置,此处应使用class属性指定被注解的dao全限定类名-->
        <mapper class="itheima.dao.IUserDao"/>
        <mapper resource="itheima2/dao/IUserDao.xml"/>
    </mappers>

</configuration>

注意
创建的接口与其对应的映射配置文件,在目录结构上必须一致,否则会报
org.apache.ibatis.binding.BindingException: Invalid bound statement (not found)
异常
在这里插入图片描述
在这里插入图片描述
测试类的框架

package itheima2;

import itheima2.dao.IUserDao;
import itheima2.domain.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.After;
import org.junit.Before;
import org.junit.Test;

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

public class MybatisTest {
    private InputStream in;
    private SqlSession session;
    private IUserDao userDao;

    @Before
//    在测试方法前执行
    public void init() {
        try {
            in = Resources.getResourceAsStream("SqlMapConfig.xml");
        } catch (Exception e) {
            e.printStackTrace();
        }
        SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(in);
        session = factory.openSession();
        userDao = session.getMapper(IUserDao.class);
    }
    @After
    public void destroy() throws Exception {
    	session.submit();//关键,一定要提交事务
        session.close();
        in.close();
    }
}
在持久层接口中添加方法

IUserDao.java

package itheima2.dao;

import itheima2.domain.User;

import java.util.List;

public interface IUserDao {
    /**
     * 查询所有用户
     * @return
     */
    List<User> findAll();

    /**
     * 保存用户
     * @param user
     */
    void saveUser(User user);

    /**
     * 更新用户
     * @param user
     */
    void updateUser(User user);

    /**
     * 删除某个用户
     * @param uid
     */
    void deleteUser(Integer uid);

    /**
     * 查询某个用户
     * @param uid
     * @return
     */
    User findOne(Integer uid);

    List<User> findByName(String name);

    int findTotal();
}
在接口的映射文件中配置

IUserDao.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="itheima2.dao.IUserDao">
    <select id="findAll" resultType="itheima2.domain.User">
        select * from user;
    </select>
    <select id="findOne" resultType="itheima2.domain.User" parameterType="java.lang.Integer" >
        select * from user where id = #{uid}
    </select>
<!--    保存用户-->
    <insert id="saveUser" parameterType="itheima2.domain.User">
        insert into user(username,address,sex,birthday)values(#{username},#{address},#{sex},#{birthday});
    </insert>

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

    <delete id="deleteUser" parameterType="java.lang.Integer">
        delete from user where id = #{uid}
    </delete>

    <select id="findByName" parameterType="string" resultType="itheima2.domain.User">
        select * from user where username like #{name}
#         select * from user where username like '%${value}%'
    </select>
    
    <select id="findTotal" resultType="int">
        select count(*) from user;
    </select>
</mapper>

细节:

  • resultType属性:
    用于指定结果集的类型
  • parameterType属性:
    用于指定传入参数类型
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
测试方法
@Test
    public void testSave() throws Exception {
        User user = new User();
        user.setUsername("李伯伯");
        user.setAddress("东华理工大学");
        user.setBirthday(new Date());
        user.setSex("男");
        System.out.println("保存之前的id"+user.getId());

        userDao.saveUser(user);
        System.out.println("保存之后的id"+user.getId());
//        提交事务
    }
    @Test
    public void testUpdate(){
        User user = new User();
        user.setId(50);
        user.setSex("女");
        user.setUsername("某师范大学");
        user.setBirthday(new Date());
        userDao.updateUser(user);
    }
    @Test
    public void testDelete() {
        userDao.deleteUser(50);
    }
    @Test
    public void testFindOne(){
        User user = userDao.findOne(51);
        System.out.println(user);
    }
    @Test
    public void findAll(){
        List<User> list = userDao.findAll();
        for (User user : list) {
            System.out.println(user);
        }
    }
    @Test
//    测试模糊查询
    public void testFindByName(){
//        如果使用的是#{} 使用 %王% 如果配置文件使用%拼接字符串,则无需使用
        List<User> list = userDao.findByName("%王%");
        for (User user : list) {
            System.out.println(user);
        }
    }
    @Test
    public void testFindbyTotal(){
        int total = userDao.findTotal();
        System.out.println(total);
    }
拓展

获取新增用户id的返回值:
在接口映射的配置文件中,做出如下配置

<!--    保存用户-->
    <insert id="saveUser" parameterType="itheima2.domain.User">
#               获取插入数据的id
#             id的属性名称(实体类的) 数据库中表的id名称 返回类型 什么时候执行获取id操作
        <selectKey keyProperty="id" keyColumn="id" resultType="int" order="AFTER">
            select  last_insert_id();
        </selectKey>

        insert into user(username,address,sex,birthday)values(#{username},#{address},#{sex},#{birthday});
    </insert>

测试方法

@Test
    public void testSave() throws Exception {
        User user = new User();
        user.setUsername("李伯伯");
        user.setAddress("东华理工大学");
        user.setBirthday(new Date());
        user.setSex("男");
        System.out.println("保存之前的id"+user.getId());

        userDao.saveUser(user);
        System.out.println("保存之后的id"+user.getId());
//        提交事务
    }

在这里插入图片描述
模糊查询

  1. #{}方法
    在这里插入图片描述
    在这里插入图片描述
  2. 拼接字符串
    在这里插入图片描述
    在这里插入图片描述
    两者#{}与${}的区别
    在这里插入图片描述
    一般使用第一种,因为第一种是基于PrepareStatement
    在这里插入图片描述

Mybatis与JDBC编程的比较

在这里插入图片描述

Mybatis的参数深入

parameterType配置参数
将实体类的包装类作为参数传递

在这里插入图片描述
注意

在这里插入图片描述
在这里插入图片描述

传递POJO包装对象

在这里插入图片描述
在接口中创建方法
List<User> findUserByVo(QueryVo vo);
在接口映射配置文件中的mapper中添加

	<select id="findUserByVo" parameterType="itheima2.domain.QueryVo" resultType="itheima2.domain.User">
        select * from user where username like #{user.username}
    </select>

因为在QueryVo中只有参数user,所以sql语句中必须带上user.
Mybatis是根据parameterType中提供的类来根据其javabean获取其参数的

QueryVo.java

package itheima2.domain;

public class QueryVo {
    User user;

    public User getUser() {
        return user;
    }

    public void setUser(User user) {
        this.user = user;
    }
}

测试包装类作为参数

@Test
    public void testFindByQueryVo(){
        QueryVo vo = new QueryVo();
        User user = new User();
        user.setUsername("%王%");
        user.setAddress("翻斗花园");
        user.setBirthday(new Date());
        user.setSex("男");
        vo.setUser(user);
        List<User> list = userDao.findUserByVo(vo);
        for (User u : list) {
            System.out.println(u);
        }
    }

在这里插入图片描述

Mybatis的输出结果封装

ResultType配置类型

在这里插入图片描述
每次类中名称都必须和数据库数据列名称一致,岂不是很麻烦?
首先修改实体类名称
在这里插入图片描述
在这里插入图片描述
IUserDao接口
在这里插入图片描述
映射配置
在这里插入图片描述
测试查询结果

@Test
    public void findAll(){
        List<User> list = userDao.findAll();
        for (User user : list) {
            System.out.println(user);
        }
    }

在这里插入图片描述
可以发现只能查出姓名,且因为大小写的原因才碰巧能够查出来
我们可以通过起别名的方法来查出数据
在这里插入图片描述

ResultMap结果类型(类与数据库中属性的映射)

在这里插入图片描述

定义

在接口的映射配置文件中配置
在这里插入图片描述
在这里插入图片描述
测试结果
在这里插入图片描述

Mybatis传统Dao开发

在这里插入图片描述

dao接口

在这里插入图片描述
在这里插入图片描述

dao接口实现类

在这里插入图片描述
在这里插入图片描述

持久层映射配置

在这里插入图片描述
在这里插入图片描述

测试类

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

SqlMapConfig.xml配置文件
配置内容

在这里插入图片描述

properties 属性

在这里插入图片描述

  1. 第一种
    在这里插入图片描述
    在这里插入图片描述
<properties>
        <property name="driver" value="com.mysql.jdbc.Driver"/>
        <property name="url" value="jdbc:mysql://localhost:3306/mybatis"/>
        <property name="username" value="root"/>
        <property name="password" value="root"/>
</properties>
<!--    配置环境-->
    <environments default="mysql">
    <!--        配置mysql的环境-->
        <environment id="mysql">
    <!--            事务类型-->
            <transactionManager type="JDBC">
            </transactionManager>
    <!--            配置数据源(连接池)-->
            <dataSource type="POOLED">
                <!--配置连接数据库的4个基本信息-->
                <property name="driver" value="${driver}"/>
                <property name="url" value="${url}"/>
                <property name="username" value="${username}"/>
                <property name="password" value="${password}"/>
            </dataSource>
        </environment>
    </environments>
  1. 第二种
    在这里插入图片描述
    在这里插入图片描述
typeAliases 类型别名

在这里插入图片描述
自定义别名
在这里插入图片描述
在这里插入图片描述

mapper映射器

在这里插入图片描述

Day3

Mybatis连接池与事务的深入

连接池技术

在这里插入图片描述

连接池分类与数据源

在这里插入图片描述

  • UNPOOLED其底层是使用DriverManger.getConnection来获取Connection对象,实现了javax.sql.DataSource接口,采用传统的获取连接的方式
  • POOED采用原始的javax.sql.DataSource规范中的连接池,底层是存在一个Arraylist的连接对象数组,需要调用的时候,选取一个存在时间最长的对象,将其拿出集合,类似于队列的形式
  • JNDI采用服务器提供的JNDI技术来获取DataSource对象,不同的服务器拿到的对象不一致。(只有web或者maven的war工程才能使用)

在这里插入图片描述

数据源的配置

在SqlMapConfig.xml中的配置
在这里插入图片描述

DataSource的存取

在这里插入图片描述

DataSourceFactory源码如下:
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

Mybatis事务控制

回顾
在这里插入图片描述
在这里插入图片描述

事务的提交方式

在这里插入图片描述
在这里插入图片描述

自动提交事务的设置

在这里插入图片描述
在这里插入图片描述

Mybatis的动态SQL语言

IF标签

在这里插入图片描述
持久层Dao接口
在这里插入图片描述
Dao映射文件的配置
在这里插入图片描述
parameterType可以写user的原因:
在Mybatis配置文件中起了别名

<typeAliases>
        <typeAlias type="itheima2.domain.User" alias="user"/>
<!--        批量别名定义,扫描整个包下的类,别名为类名-->
        <package name="itheima2.domain"/>
</typeAliases>

测试
在这里插入图片描述

where标签

在这里插入图片描述
持久层Dao映射文件配置
在这里插入图片描述

foreach标签

在这里插入图片描述
创建QueryVo

package itheima2.domain;

import java.io.Serializable;
import java.util.List;

public class QueryVo implements Serializable {
    User user;
    List<Integer> ids;
    public User getUser() {
        return user;
    }

    public List<Integer> getIds() {
        return ids;
    }

    public void setIds(List<Integer> ids) {
        this.ids = ids;
    }

    public void setUser(User user) {
        this.user = user;
    }
}

Dao接口中的方法
在这里插入图片描述
Dao映射配置文件添加的代码
在这里插入图片描述
foreach标签用于遍历集合:

  • collection:代表要遍历的集合元素,注意编写时不要写#{}
  • open:代表语句的开始部分
  • close:代表结束部分
  • item:代表遍历集合的每一个元素
  • sperator:代表sql语句中元素的分隔符

测试代码
在这里插入图片描述

简化编写的SQL片段

<sql>标签
在这里插入图片描述
引用sql语句使用:

<include refid="defaultSql"/>

Mybatis多表查询之一对多

在这里插入图片描述

一对一查询

在这里插入图片描述

Way 1

定义账户信息的实体类Account

package itheima2.domain;

import java.io.Serializable;

public class Account implements Serializable {
    private int id;
    private int uid;
    private double money;

    public int getId() {
        return id;
    }

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

    public int getUid() {
        return uid;
    }

    public void setUid(int uid) {
        this.uid = uid;
    }

    public double getMoney() {
        return money;
    }

    public void setMoney(double money) {
        this.money = money;
    }

    @Override
    public String toString() {
        return  "id=" + id +
                ", uid=" + uid +
                ", money=" + money;
    }
}

分析数据库编写SQL语句
用户表里id为账户表中的UID的外键
在这里插入图片描述

select a.*,b.username,b.address  from account as a , user as b where b.id = a.UID

在这里插入图片描述
创建AccountUser类

package itheima2.domain;

public class AccountUser extends Account{
    private String username;
    private String address;

    public String getUsername() {
        return username;
    }

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

    public String getAddress() {
        return address;
    }

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

    @Override
    public String toString() {
        return super.toString()  +
                ", username='" + username + '\'' +
                ", address='" + address +'\'';
    }
}

定义账户的持久层Dao接口IAccountDao

package itheima2.dao;

import itheima2.domain.Account;
import itheima2.domain.AccountUser;

import java.util.List;

public interface IAccountDao {
    /**
     * 查询所有账户信息
     * @return
     */
    List<Account> findAll();

    /**
     * 查询所有账户,同时获取到当前账户的所属用户信息
     * @return
     */
    List<AccountUser> findAllAccount();
}

定义账户的映射配置文件IAccountDao.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="itheima2.dao.IAccountDao">
    <select id="findAll" resultType="account">
        select * from account
    </select>
    <select id="findAllAccount" resultType="accountUser">
        select a.*,b.username,b.address  from account as a , user as b where b.id = a.UID
    </select>
</mapper>

在这里插入图片描述
创建测试类AccountTest

package itheima2;

import itheima2.dao.IAccountDao;
import itheima2.domain.Account;
import itheima2.domain.AccountUser;
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.InputStream;
import java.util.List;

public class AccountTest {
    private InputStream in;
    private SqlSession session;
    private IAccountDao Iac;

    @Before
    public void init() throws Exception {
        in = Resources.getResourceAsStream("SqlMapConfig.xml");
        SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(in);
        session = factory.openSession(true);
        Iac = session.getMapper(IAccountDao.class);
    }

    @After
    public void destroy() throws Exception {
        in.close();
        session.close();
    }

    @Test
    public void testFindAll(){
        List<Account> list = Iac.findAll();
        for (Account account : list) {
            System.out.println(account);
        }
    }
    @Test
    public void testFindAllAccount(){
        List<AccountUser> list = Iac.findAllAccount();
        for (AccountUser accountUser : list) {
            System.out.println(accountUser);
        }
    }
}

tips:
定义专门的po类作为输出类型,其中定义了sql查询结果集所有的字段。此方法较为简单,企业中使用普遍

Way2

在这里插入图片描述
在 Account 类中加入 User 类的对象作为 Account 类的一个属性。

public class Account implements Serializable {
    private Integer id;
    private Integer uid;
    private Double money;
    private User user;

    public User getUser() {
        return user;
    }

    public void setUser(User user) {
        this.user = user;
    }

    public Integer getId() {
        return id;
    }

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

    public Integer getUid() {
        return uid;
    }

    public void setUid(Integer uid) {
        this.uid = uid;
    }

    public Double getMoney() {
        return money;
    }

    public void setMoney(Double money) {
        this.money = money;
    }

    @Override
    public String toString() {
        return "Account [id=" + id + ", uid=" + uid + ", money=" + money + "]";
    }
}

新增 IAccountDao 接口中的方法

	/**
     * 查询所有账户,同时获取到当前账户的所属用户信息第二种方法
     * @return List<Account>
     */
    List<Account> findAllAccount2();

注意:第二种方式,将返回值改 为了 Account 类型。
因为 Account 类中包含了一个 User 类的对象,它可以封装账户所对应的用户信息。

修改IAccountDao.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="itheima2.dao.IAccountDao">
    <!--    定义封装account和user的resultMap-->
    <resultMap id="accountUserMap" type="account">
        <id property="id" column="aid"/>
        <result property="uid" column="uid"/>
        <result property="money" column="money"/>
    <!--        property:映射到类中的对象 javaType:指定pojo(实体类)中属性的类型 两个表是由uid联系在一起的 -->
        <association property="user" javaType="itheima2.domain.User">
            <id column="id" property="id"/>
            <result column="username" property="userName"/>
            <result column="address" property="address"/>
            <result column="sex" property="sex"/>
            <result column="birthday" property="birthday"/>
        </association>
    </resultMap>
    <select id="findAll" resultType="account">
        select * from account
    </select>
    <select id="findAllAccount" resultType="accountUser">
        select a.*,b.username,b.address  from account as a, user as b where b.id = a.UID
    </select>
    <select id="findAllAccount2" resultMap="accountUserMap">
        select u.*,a.id as aid,a.MONEY from account as a, user as u where u.id=a.uid;
    </select>
</mapper>
一对多查询

在这里插入图片描述
在这里插入图片描述
左查询:即使表2没有对应项,也保留表1的查询项
修改User类

package itheima2.domain;

import java.io.Serializable;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.List;

public class User implements Serializable {
    private Integer id;
    private String userName;
    private String address;
    private String sex;
    private Date birthday;

    private List<Account> accounts;

    private static SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");

    public List<Account> getAccounts() {
        return accounts;
    }

    public void setAccounts(List<Account> accounts) {
        this.accounts = accounts;
    }

    public Integer getId() {
        return id;
    }

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

    public String getUserName() {
        return userName;
    }

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

    public String getAddress() {
        return address;
    }

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

    public String getSex() {
        return sex;
    }

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

    public Date getBirthday() {
        return birthday;
    }

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

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

修改IUserDao.xml,添加如下代码

<resultMap id="userAccountMap" type="User">
        <id property="id" column="id"/>
        <result property="userName" column="username"/>
        <result property="address" column="address"/>
        <result property="sex" column="sex"/>
        <result property="birthday" column="birthday"/>
<!--        配置User类型的对象中accounts集合的映射-->
<!--        JavaType是用来指定pojo中属性的类型,而ofType指定的是 映射到list集合属性中pojo的类型-->
        <collection property="accounts" ofType="itheima2.domain.Account">
            <id property="id" column="aid"/>
            <result property="uid" column="uid"/>
            <result property="money" column="money"/>
        </collection>
</resultMap>
    <select id="findAll" resultMap="userAccountMap">
        select u.*,a.ID as aid,a.MONEY from user as u left join account as a on u.id = a.UID;
    </select>

在这里插入图片描述
测试

@Test
    public void findAll(){
        List<User> list = userDao.findAll();
        for (User user : list) {
            System.out.println(user);
            System.out.print(user.getAccounts());
        }
    }

在这里插入图片描述

Mybatis查询之多对多查询

实现 Role 到 User 多对多

在这里插入图片描述

用户与角色的关系模型

在这里插入图片描述
角色表
在这里插入图片描述
用户角色中间表
在这里插入图片描述
用户表
在这里插入图片描述

业务要求

在这里插入图片描述
分析
当使用语句
在这里插入图片描述
查询出的表为
在这里插入图片描述
我们继续将这张表和用户表的相同id字段左外连接,并且不查询某些重复无效部分

select r.*,u.id as uid,u.username as username,u.birthday as birthday,u.sex as sex,u.address as address
    from role as r
    left join user_role as ur on r.id = ur.RID
    left join user as u on ur.UID = u.id

在这里插入图片描述

在Role类中添加一个数组
private List<User> users;
修改配置文件中id="findAll"的配置项
配置文件

<?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="itheima2.dao.IRoleDao">
    <resultMap id="roleMap" type="Role">
    <id property="roleId" column="id"/>
    <result property="roleName" column="role_name"/>
    <result property="roleDesc" column="role_desc"/>
    <collection property="users" ofType="User">
        <id property="id" column="uid"/>
        <result property="userName" column="username"/>
        <result property="birthday" column="birthday"/>
        <result property="sex" column="sex"/>
        <result property="address" column="address"/>
    </collection>
</resultMap>

    <select id="findAll" resultMap="roleMap">
        select r.*,u.id as uid,u.username as username,u.birthday as birthday,u.sex as sex,u.address as address
        from role as r
                 left join user_role as ur on r.id = ur.RID
                 left join user as u on ur.UID = u.id
    </select>
</mapper>

测试
在这里插入图片描述
在这里插入图片描述

实现User到Role的多对多

在这里插入图片描述

业务要求

在这里插入图片描述
分析sql语句

select u.*,r.id as rid,r.role_name,r.role_desc from user as u
        left join user_role as ur on u.id=ur.UID
        left join role as r on r.ID=ur.RID

在这里插入图片描述

配置文件配置

<?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="itheima2.dao.IUserDao">
    <resultMap id="userMap" type="itheima2.domain.User">
<!--        property为类中对应属性-->
        <id column="id" property="id"/>
        <result column="username" property="userName"/>
        <result column="sex" property="sex"/>
        <result column="address" property="address"/>
        <result column="birthday" property="birthday"/>
        <collection property="roles" ofType="Role">
            <id column="rid" property="roleId"/>
            <id column="ROLE_NAME" property="roleName"/>
            <id column="ROLE_DESC" property="roleDesc"/>
        </collection>
    </resultMap>
    <select id="findAll" resultMap="userMap">
        select u.*,r.id as rid,r.role_name,r.role_desc from user as u
        left join user_role as ur on u.id=ur.UID
        left join role as r on r.ID=ur.RID
    </select>
</mapper>

测试
在这里插入图片描述
在这里插入图片描述

扩展:JNDI数据源配置 (了解即可)

JNDI:Java Naming and Directory Interface。是SUN公司推出的一套规范,属于JavaEE技术之一。目的是模仿windows系统中的注册表。在服务器中注册数据源

注册表结构

在这里插入图片描述
在这里插入图片描述

Day4

Mybatis延迟加载

今天的内容
在这里插入图片描述

延迟加载内容

在这里插入图片描述
在这里插入图片描述

实现需求

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

使用 assocation 实现一对一延迟加载

select属性指定的内容: 查询用户的唯一标识 如 itheima.dao.IUserDao.findById
column属性指定的内容: 用户根据id查询时,所需要的参数的值 如uid(为数据库中列名称)
而在IAccount的mapper配置中,其代码是这样的

<resultMap id="accountUserMap" type="account">
        <id property="id" column="aid"/>
        <result property="uid" column="uid"/>
        <result property="money" column="money"/>
    <!--        property:映射到类中的对象 javaType:指定pojo(实体类)中属性的类型 两个表是由uid联系在一起的 -->
        <association property="user" javaType="user" select="itheima.dao.IUserDao.findById" column="uid"/>
</resultMap>

IUserDao的配置文件

<select id="findById" parameterType="INT" resultType="user">
        select * from user where id = #{uid}
</select>

开启Mybatis延迟加载策略
在这里插入图片描述
在SqlMapConfig.xml
configuration标签里添加代码
要写在properties标签后
在这里插入图片描述
在这里插入图片描述如果出现莫名其妙的报错,请注意jdk版本是否为16,如为16则将pom中的mybatis版本改成3.5.7,以及查看各个配置文件中的全限定类名是否正确
没开启与开启了延迟加载的区别
在这里插入图片描述

使用 Collection 实现延迟一对多加载

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
IAccount接口
在这里插入图片描述
IUserdao.xml
在这里插入图片描述
IAccount.xml
在这里插入图片描述
在这里插入图片描述
由于延迟加载的缘故,语句执行为
select * from user
在这里插入图片描述

Mybatis缓存

缓存 存在于内存中的临时数据
为什么使用缓存 减少和数据库的交互次数,提高执行效率
能使用缓存的数据

  1. 经常查询
  2. 不经常改变
  3. 数据的正确与否对最终结果影响不大的

不能使用缓存的数据

  1. 经常改变
  2. 数据的正确与否对结果影响很大
  3. 商品的库存,银行的汇率,股市排价
一级缓存

在这里插入图片描述
在这里插入图片描述
编写IUserDao
在这里插入图片描述
映射配置文件
在这里插入图片描述
测试方法
在这里插入图片描述
在这里插入图片描述
一级缓存是Sqlsession范围的缓存,调用Sqlsession的修改,添加,删除,commit,close等方法时,就会清空一切一级缓存
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
输出为false

二级缓存

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

开启与关闭

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
谁查询了数据 就将查询过的数据填充到新的对象里去,所以还是新对象,不与一级缓存一样

Mybatis注解开发

在这里插入图片描述

常用注解说明

在这里插入图片描述
当项目某个接口存在配置文件时,无论你是否在Mybatis配置文件中映射选择全限定类名还是配置文件
只要存在配置文件,就会报错

在这里插入图片描述

CRUD操作

编写实体类
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
id的意思为主键
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

复杂关系映射开发

实现复杂关系映射之前我们可以在映射文件中通过配置来实现,在使用注解开发时我们需要借助@Results 注解,@Result 注解,@One 注解,@Many 注解。
一个表中的记录对应另一个表的一个记录 一般使用立即加载
一个表中的记录对应另一个表的多个记录 一般使用延迟加载

一对一

在这里插入图片描述
需求:
加载账户信息时并且加载该账户的用户信息,根据情况可实现延迟加载。(注解方式实现)

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

在这里插入图片描述
测试
在这里插入图片描述

一对多

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

二级缓存的配置

在这里插入图片描述

Spring(中间层框架,用于整合其余层)

spring 是用来整合 j2ee各个层之间的中间层。是各个层次之间是独立的,提高层与层之间的松耦合。使各个层之间对立成为对立的模块。
在这里插入图片描述
发展历程
在这里插入图片描述
优势

  • 方便解耦,简化开发
    通过 Spring 提供的 IoC 容器,可以将对象间的依赖关系交由 Spring 进行控制,避免硬编码所造
    成的过度程序耦合。用户也不必再为单例模式类、属性文件解析等这些很底层的需求编写代码,可
    以更专注于上层的应用。
  • AOP 编程的支持
    通过 Spring 的 AOP 功能,方便进行面向切面的编程,许多不容易用传统 OOP 实现的功能可以
    通过 AOP 轻松应付。
    在这里插入图片描述
    体系结构
    在这里插入图片描述

IoC

程序的耦合与解耦

在这里插入图片描述
在这里插入图片描述
应该做到
在这里插入图片描述
在这里插入图片描述
当时我们讲解 jdbc 时,是通过反射来注册驱动的,代码如下:
Class.forName(“com.mysql.jdbc.Driver”);//此处只是一个字符串
此时的好处是,我们的类中不再依赖具体的驱动类,此时就算删除 mysql 的驱动 jar 包,依然可以编译(运行就不要想了,没有驱动不可能运行成功的)。
同时,也产生了一个新的问题,mysql 驱动的全限定类名字符串是在 java 类中写死的,一旦要改还是要修改源码。
解决这个问题也很简单,使用配置文件配置。

工厂模式(解耦)

在这里插入图片描述

IoC(控制反转)

在这里插入图片描述
在这里插入图片描述
小例子来模拟工厂模式
在这里插入图片描述

public class BeanFactory {
    private static Properties props;
    private static Map<String,Object> beans;
    static {
        try {
            props = new Properties();
            InputStream is = BeanFactory.class.getClassLoader().getResourceAsStream("bean.properties");
            props.load(is);

            beans = new HashMap<>();
            Enumeration<Object> keys = props.keys();
//            遍历配置文件中各个部分 将他们存进beans这个map中 形成单例模式(即只需创建一个对象)
            while(keys.hasMoreElements()){
                String key = keys.nextElement().toString();
                String beanPath = props.getProperty(key);
                //反射创建对象
                Object value = Class.forName(beanPath).getDeclaredConstructor().newInstance();
                beans.put(key,value);
            }
        } catch (Exception e) {
            throw new ExceptionInInitializerError("初始化Properties失败");
        }
    }
    public static Object getBean(String beanName){
        return beans.get(beanName);
    }
}
  • 可以通过读取全限定类名来创建对象,从而达到解耦的目的
  • 而全限定类名可以通过读取文件来达成,这也达到了解耦的目的
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述

使用 spring 的 IOC 解决程序耦合

前期准备

本章我们使用的案例是,账户的业务层和持久层的依赖关系解决。在开始 spring 的配置之前,我们要先准备一下环境。
由于我们是使用 spring 解决依赖关系,并不是真正的要做增删改查操作,所以此时我们没必要写实体
类。并且我们在此处使用的是 java 工程,不是 java web 工程。

官网:http://spring.io/
下载地址:
http://repo.springsource.org/libs-release-local/org/springframework/spring
解压:(Spring 目录结构:)

  • docs :API 和开发规范.
  • libs :jar 包和源码.
  • schema :约束.
    在这里插入图片描述
基于XML的Spring搭建

基于maven工程,导入spring坐标

<dependency>
     <groupId>org.springframework</groupId>
     <artifactId>spring-context</artifactId>
     <version>5.0.20.RELEASE</version>
</dependency>

在这里插入图片描述
给配置文件导入约束

<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans.xsd">
        
</bean>

在这里插入图片描述
测试

package itheima.ui;

import itheima.dao.AccountDao;
import itheima.service.AccountService;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class Client {
    /**
     * 获取springIOC的核心容器
     * @param args
     */
    public static void main(String[] args) {
        // 获取核心容器对象(Map)
        ApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml");
        // 根据id获取bean对象
        AccountService as = (AccountService) ac.getBean("accountService");
        AccountDao adao = ac.getBean("accountDao",AccountDao.class);
        System.out.println(as);
        System.out.println("--------");
        System.out.println(adao);
    }
}

Spring 基于 XML 的 IOC 细节

在这里插入图片描述
在这里插入图片描述
BeanFactory 和 ApplicationContext 的区别
很明显 ApplicationContext用于单例模式
BeanFactory用于多例模式
当需要使用的时候就创建对应对象,多次使用则多次创建不同对象,为多例对象
当每次使用的时候使用的是同一个对象,为单例对象

在这里插入图片描述
在这里插入图片描述
ClassPath只能读取加载类路径下的配置文件
FileSystem可以加载磁盘任意路径下的配置文件(必须有权限)

IOC 中 bean 标签和管理对象细节

标签
在这里插入图片描述
global Session
在这里插入图片描述

在这里插入图片描述
当用户获取页面时,会获得验证码,此时它存在某个服务器的session上,当用户提交时,发现原发送验证码的服务器已经满负荷了,于是提交到其他服务器上,但是其他服务器并没有原服务器的session信息,这时就要使用全局session,所有服务器都能获取到此session
生命周期
在这里插入图片描述

创建Bean的三种方式

第一种方式:使用默认无参构造函数,如
<bean id="accountService" class="com.itheima.service.impl.AccountServiceImpl"/>
第二种:spring 管理实例工厂-使用普通实例工厂的方法创建对象,如

<bean id="instanceFactory" class="itheima.factory.InstanceFactory"/>
<bean id="accountService" factory-bean="instanceFactory" factory-method="getAccountService"/>

第三种:spring 管理静态工厂-使用静态工厂的方法创建对象,如

<bean id="accountService2" class="itheima.factory.StaticFactory" factory-method="getAccountService"/>
依赖注入

依赖注入:Dependency Injection。它是 spring 框架核心 ioc 的具体实现。
我们的程序在编写时,通过控制反转,把对象的创建交给了 spring,但是代码中不可能出现没有依赖的情况。
ioc 解耦只是降低他们的依赖关系,但不会消除。例如:我们的业务层仍会调用持久层的方法。
那这种业务层和持久层的依赖关系,在使用 spring 之后,就让 spring 来维护了。
简单的说,就是坐等框架把持久层对象传入业务层,而不用我们自己去获取。
在这里插入图片描述
如果是经常变化的数据,则不适合注入的方式
构造函数注入
例如
AccountServiceImpl.java

package itheima.service.impl;

import itheima.service.AccountService;

import java.util.Date;

public class AccountServiceImpl implements AccountService {
    private String name;
    private Integer age;
    private Date birthday;

    public AccountServiceImpl(String name, Integer age, Date birthday) {
        this.name = name;
        this.age = age;
        this.birthday = birthday;
    }

    @Override
    public void saveAccount() {
        System.out.println("保存账户");
    }
    public void init(){
        System.out.println("对象初始化了");
    }
    public void destroy(){
        System.out.println("对象销毁了");
    }

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

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

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

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

配置文件

	<bean id="accountService" class="itheima.service.impl.AccountServiceImpl">
        <constructor-arg name="name" value="LishiBo"></constructor-arg>
        <constructor-arg name="age" value="20"></constructor-arg>
        <constructor-arg name="birthday" ref="now"></constructor-arg>
    </bean>
    <bean id="now" class="java.util.Date"></bean>

在这里插入图片描述
Set方法注入

<bean id="accountService2" class="itheima.service.impl.AccountServiceImpl" scope="singleton">
        <property name="name" value="Bobo"></property>
        <property name="age" value="22"></property>
        <property name="birthday" ref="now"></property>
</bean>

在这里插入图片描述
使用 p 名称空间注入数据(本质还是调用 set 方法)

<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:p="http://www.springframework.org/schema/p" 	新增了这句话!
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans.xsd">
        <bean id="accountService3"
          class="itheima.service.impl.AccountServiceImpl"
          p:name="test" p:age="21" p:birthday-ref="now"/>
</beans>

复杂类型注入
在这里插入图片描述

<bean id="accountService4" class="itheima.service.impl.AccountServiceImpl2">
        <property name="myStrs">
            <array>
                <value>AAA</value>
                <value>BBB</value>
                <value>CCC</value>
            </array>
        </property>
        <property name="myList">
            <list>
                <value>DDD</value>
                <value>EEE</value>
                <value>FFF</value>
            </list>
        </property>
        <property name="mySet">
            <set>
                <value>GGG</value>
                <value>HHH</value>
                <value>III</value>
            </set>
        </property>
        <property name="myMap">
            <map>
                <entry key="map1" value="1"/>
                <entry key="map2">
                    <value>2</value>
                </entry>
            </map>
        </property>
        <property name="myProps">
            <props>
                <prop key="p1">my1</prop>
                <prop key="p2">my2</prop>
            </props>
        </property>
</bean>

Spring 配置文件中提示的配置
在这里插入图片描述

基于注解的IOC配置(注解标签介绍)

在这里插入图片描述
学习基于注解的 IoC 配置,大家脑海里首先得有一个认知,即注解配置和 xml 配置要实现的功能都是一样
的,都是要降低程序间的耦合。只是配置的形式不一样。
关于实际的开发中到底使用xml还是注解,每家公司有着不同的使用习惯。所以这两种配置方式我们都需要掌
握。
我们在讲解注解配置时,采用上一章节的案例,把 spring 的 xml 配置内容改为使用注解逐步实现。
在这里插入图片描述
首先要告诉spring要扫描的包在哪,不然写了注解也只是无头苍蝇
bean.xml

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/context
        http://www.springframework.org/schema/context/spring-context.xsd">
<!--    告知spring在创建容器时,要扫描的包 配置所需要的标签不在beans的约束中
        而是context名称空间和约束-->
    <context:component-scan base-package="itheima"/>
</beans>

用于创建对象的
  • @Component:用于把当前类对象存入spring容器中
    属性:
    value:用于指定bean的id,默认值是当前类名且首字母改小写
    在这里插入图片描述
    在这里插入图片描述
用于注入数据的
  • @Autowired
    作用:自动按照类型注入。
    1. 只要容器(spring中ioc的容器为map)中有唯一的bean对象类型(map的value的数据类型)和要注入的变量类型匹配,就可以注入成功,如果ioc容器中没有任何的bean类型和要注入的变量类型匹配,则报错
    2. 如果IOC有多个类型匹配上时:会自动查看类中的对象名,比对是否和map的key名称一致,如果一致则不报错且直接使用。
  • @Qualifier
    作用:在按照类中注入的基础之上再按照名称注入。"value="可以默认不写,他在给类成员注入时不能单独使用,要和Autowired共同使用,但是给方法参数注入时可以
    属性:
    value:用于指定注入bean的id
  • @Resource
    作用:直接按照bean的id注入,可以独立使用
    属性:
    name:用于指定注入bean的id
    注意,使用时需要导入依赖
		<dependency>
            <groupId>javax.annotation</groupId>
            <artifactId>javax.annotation-api</artifactId>
            <version>1.3.2</version>
        </dependency>

以上三个注解只能用于注入bean类型的数据,基本类型和String无法使用。
而集合类型注入只能通过xml实现

  • @Value
    作用:用于注入基本类型和String类型的数据
    属性:
    value:用于指定数据的值,可以用于spring中Spel(spring的el表达式)
    Spel写法:${表达式}
    el表达式写在哪个文件里就是作用于哪里,mybatis的配置文件的el表达式作用于mybatis,spring也如此
    在这里插入图片描述
改变作用范围的
  • @Scope
    作用:指定 bean 的作用范围。
    属性:
    value:指定范围的值。
    取值: singleton prototype request session globalsession
和生命周期相关的
  • @PostConstruct
    用于指定初始化方法。
  • @PreDestroy
    用于指定销毁方法。
关于 Spring 注解和 XML 的选择问题

在这里插入图片描述
基于注解的 spring IoC 配置中,bean 对象的特点和基于 XML 配置是一模一样的

案例:基于XML的IOC简易案例

数据库
在这里插入图片描述
先导入pom依赖

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>org.example</groupId>
    <artifactId>Spring_Day2</artifactId>
    <version>1.0-SNAPSHOT</version>
    <packaging>jar</packaging>

    <dependencies>
<!--        spring-->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>5.3.13</version>
        </dependency>
<!--        Resource注解需要的-->
        <dependency>
            <groupId>javax.annotation</groupId>
            <artifactId>javax.annotation-api</artifactId>
            <version>1.3.2</version>
        </dependency>
        <dependency>
            <groupId>commons-dbutils</groupId>
            <artifactId>commons-dbutils</artifactId>
            <version>1.4</version>
        </dependency>

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

        <dependency>
            <groupId>c3p0</groupId>
            <artifactId>c3p0</artifactId>
            <version>0.9.1.2</version>
        </dependency>

        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.13</version>
            <scope>test</scope>
        </dependency>
    </dependencies>

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

</project>

这里数据库是8.0.多,所以在设置数据库路径的时候,一定要注意好时区,不然一定会报错,所以要这样设置
<property name="jdbcUrl" value="jdbc:mysql://localhost:3306/spring?serverTimezone=UTC"></property>

结构
在这里插入图片描述
IAccountDaoImpl.java

package itheima2.dao.impl;

import itheima2.dao.IAccountDao;
import itheima2.domain.IAccount;
import org.apache.commons.dbutils.QueryRunner;
import org.apache.commons.dbutils.handlers.BeanHandler;
import org.apache.commons.dbutils.handlers.BeanListHandler;

import java.util.List;

public class IAccountDaoImpl implements IAccountDao {
    private QueryRunner runner;

    public void setRunner(QueryRunner runner) {
        this.runner = runner;
    }

    @Override
    public List<IAccount> findAllAccount() {
        try{
            return runner.query("select * from account",new BeanListHandler<>(IAccount.class));
        }catch(Exception e){
            throw new RuntimeException(e);
        }
    }

    @Override
    public IAccount findAccountById(int id) {
        try{
            return runner.query("select * from account where id = ?",new BeanHandler<>(IAccount.class),id);
        }catch(Exception e){
            throw new RuntimeException(e);
        }
    }

    @Override
    public void saveAccount(IAccount account) {
        try{
            runner.update("insert into account(name,money) values(?,?)",account.getName(),account.getMoney());
        }catch(Exception e){
            throw new RuntimeException(e);
        }
    }

    @Override
    public void updateAccount(IAccount a) {
        try{
            runner.update("update account set name = ?,money = ? where id = ?",a.getName(),a.getMoney(),a.getId());
        }catch(Exception e){
            throw new RuntimeException(e);
        }
    }

    @Override
    public void deleteAccount(Integer accountId) {
        try{
            runner.update("delete from account where id = ?",new BeanHandler<>(IAccount.class),accountId);
        }catch(Exception e){
            throw new RuntimeException(e);
        }
    }
}

IAccount.java

package itheima2.domain;

import java.io.Serializable;

public class IAccount implements Serializable {
    private Integer id;
    private String name;
    private float money;

    public Integer getId() {
        return id;
    }

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

    public String getName() {
        return name;
    }

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

    public float getMoney() {
        return money;
    }

    public void setMoney(float money) {
        this.money = money;
    }

    @Override
    public String toString() {
        return "IAccount{" +
                "id=" + id +
                ", name='" + name + '\'' +
                ", money=" + money +
                '}';
    }
}

IAccountServiceImpl.java

package itheima2.service.impl;

import itheima2.dao.IAccountDao;
import itheima2.domain.IAccount;
import itheima2.service.IAccountService;

import java.util.List;

public class IAccountServiceImpl implements IAccountService {
    private IAccountDao accountDao;

    public void setAccountDao(IAccountDao accountDao) {
        this.accountDao = accountDao;
    }

    @Override
    public List<IAccount> findAllAccount() {
       return accountDao.findAllAccount();
    }

    @Override
    public IAccount findAccountById(Integer id) {
        return accountDao.findAccountById(id);
    }

    @Override
    public void saveAccount(IAccount account) {
        accountDao.saveAccount(account);
    }

    @Override
    public void updateAccount(IAccount account) {
        accountDao.updateAccount(account);
    }

    @Override
    public void deleteAccount(Integer accountId) {
        accountDao.deleteAccount(accountId);
    }
}

bean.xml

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/context
        http://www.springframework.org/schema/context/spring-context.xsd">
<!--    告知spring在创建容器时,要扫描的包 配置所需要的标签不在beans的约束中
        而是context名称空间和约束-->

<!--    <context:component-scan base-package="itheima"/>-->
    
<!--    配置Service-->
    <bean id="accountService" class="itheima2.service.impl.IAccountServiceImpl">
        <property name="accountDao" ref="accountDao"/>
    </bean>

<!--    配置Dao对象-->
    <bean id="accountDao" class="itheima2.dao.impl.IAccountDaoImpl">
<!--        注入QueryRunner-->
        <property name="runner" ref="runner"/>
    </bean>

<!--    配置QueryRunner 防止线程干扰 使用多例-->
    <bean id="runner" class="org.apache.commons.dbutils.QueryRunner" scope="prototype">
<!--        注入数据源 但是没有set方法 使用构造函数方法-->
        <constructor-arg name="ds" ref="dataSource"/>
    </bean>

<!--    配置数据源-->
    <bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
<!--        连接数据库的必备信息-->
        <property name="driverClass" value="com.mysql.cj.jdbc.Driver"></property>
        <property name="jdbcUrl" value="jdbc:mysql://localhost:3306/spring?serverTimezone=UTC"></property>
        <property name="user" value="root"></property>
        <property name="password" value="582211"></property>
    </bean>
</beans>

测试类

import itheima2.domain.IAccount;
import itheima2.service.IAccountService;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

import java.util.List;

/**
 * 单元测试
 */
public class AccountServiceTest {
    ApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml");
    @Test
    public void testFindAll() {
        //1.获取容器
        IAccountService service = ac.getBean("accountService", IAccountService.class);
        //2.执行方法
        List<IAccount> list = service.findAllAccount();
        for (IAccount acc : list) {
            System.out.println(acc);
        }
    }
    @Test
    public void testFindOne() {
        IAccountService service = ac.getBean("accountService", IAccountService.class);
        IAccount account = service.findAccountById(2);
        System.out.println(account);
    }
    @Test
    public void testSave() {
        IAccountService service = ac.getBean("accountService", IAccountService.class);
        IAccount account = new IAccount();
        account.setName("李世礴");
        account.setMoney(10000.26f);
        service.saveAccount(account);
    }
    @Test
    public void testUpdate() {
        IAccountService service = ac.getBean("accountService", IAccountService.class);
        IAccount account = service.findAccountById(3);
        account.setName("Pedestrian");
        service.updateAccount(account);
    }
    @Test
    public void testDelete() {
        IAccountService service = ac.getBean("accountService", IAccountService.class);
        service.deleteAccount(1);
    }
}

测试结果
在这里插入图片描述

基于注解的IOC配置

原方法
bean.xml

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/context
        http://www.springframework.org/schema/context/spring-context.xsd">
<!--    告知spring在创建容器时,使用注解时要扫描的包
        配置所需要的标签不在beans的约束中
        而是context名称空间和约束-->
    <context:component-scan base-package="itheima2"/>

<!--    配置QueryRunner 防止线程干扰 使用多例-->
    <bean id="runner" class="org.apache.commons.dbutils.QueryRunner" scope="prototype">
<!--        注入数据源 但是没有set方法 使用构造函数方法-->
        <constructor-arg name="ds" ref="dataSource"/>
    </bean>

<!--    配置数据源-->
    <bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
<!--        连接数据库的必备信息-->
        <property name="driverClass" value="com.mysql.cj.jdbc.Driver"></property>
        <property name="jdbcUrl" value="jdbc:mysql://localhost:3306/spring?serverTimezone=UTC"></property>
        <property name="user" value="root"></property>
        <property name="password" value="582211"></property>
    </bean>
</beans>

IAccountServiceImpl
在这里插入图片描述
IAccountDaoImpl
在这里插入图片描述
新方法

  • @Configuration
    作用:
    用于指定当前类是一个 spring 配置类,当创建容器时会从该类上加载注解。获取容器时需要使用
    AnnotationApplicationContext(有@Configuration 注解的类.class)。
    属性:
    value:用于指定配置类的字节码
    细节:
    当配置类字节码作为AnnotationConfigApplicationContext对象创建的参数时,该注解可以不写
    意思是,如果AnnotationConfigApplicationContext构造函数所填的字节码对应的类里没有配置的Bean注解方法,那么可以使用@ComponentScan{}添加需要扫描的包(包含了真正的配置了Bean的类),其次,这个真正的配置了Bean的类必须加上@Configuration注解,如果想不加,那么可以在AnnotationConfigApplicationContext构造函数的参数加上真正的配置了Bean的类的字节码
    例如
    在这里插入图片描述
    在这里插入图片描述
    这里加上了JdbcConfig.class
    那么其实在SpringConfiguration中的@ComponentScan就不用加上JdbcConfig类的对应包名,并且Configuration注解也不用写
    在这里插入图片描述

  • @ComponentScan 作用:用于指定 spring 在初始化容器时要扫描的包。作用和在 spring 的 xml 配置文件中的:<context:component-scan base-package=“com.itheima”/>是一样的。
    属性:
    value
    basePackages:用于指定要扫描的包。和该注解中的 value 属性作用一样。
    在这里插入图片描述

  • @Bean
    作用:
    该注解只能写在方法上,表明使用此方法创建一个对象,并且放入 spring 容器。(单例)
    属性:
    name:给当前@Bean 注解方法创建的对象指定一个名称(即 bean 的 id),默认是当前方法的名称
    细节:
    当我们使用注解配置方法时,如果方法有参数(like dataSource),spring框架会去容器中查找有没有可用的bean对象,查找的方式和Autowired注解的作用是一样的
    在这里插入图片描述
    创建AnnotationConfigApplicationContext对象
    ApplicationContext ac2 = new AnnotationConfigApplicationContext(SpringConfiguration.class);
    测试

@Test
    public void testFindAll2() {
        //1.获取容器
        IAccountService service = ac2.getBean("accountService", IAccountService.class);
        //2.执行方法
        List<IAccount> list = service.findAllAccount();
        for (IAccount acc : list) {
            System.out.println(acc);
        }
    }

当有多个相同类型的Bean对象
在这里插入图片描述
他会默认选取与方法参数名相同的Bean对象
同时,也可以自己配置,使用@Qualifier
在这里插入图片描述

  • @Import
    作用:
    用于导入其他配置类,在引入其他配置类时,被导入的配置类可以不用再写@Configuration 注解。当然,写上也没问题。
    属性:
    value[]:用于指定其他配置类的字节码。
    使用import后,有注解的为父配置类,被导入的子配置类
    截图
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    为了解耦,需要将createDataSource方法改造,采用读取配置文件
  • @PropertySource
    作用:
    用于加载.properties 文件中的配置。例如我们配置数据源时,可以把连接数据库的信息写到 properties 配置文件中,就可以使用此注解指定 properties 配置文件的位置。
    属性:
    value[]:用于指定 properties 文件位置。如果是在类路径下,需要写上 classpath:,也可以带上包名
    在这里插入图片描述
package config;

import com.mchange.v2.c3p0.ComboPooledDataSource;
import org.apache.commons.dbutils.QueryRunner;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Scope;

import javax.sql.DataSource;

public class JdbcConfig {
    @Value("${jdbc.driver}")
    private String driver;

    @Value("${jdbc.url}")
    private String url;

    @Value("${jdbc.username}")
    private String username;

    @Value("${jdbc.password}")
    private String password;
    @Bean(name = "runner")
    @Scope(value = "prototype")
    public QueryRunner createQueryRunner(DataSource dataSource){
        return new QueryRunner(dataSource);
    }
    @Bean(name="dataSource")
    public DataSource createDataSource(){
        ComboPooledDataSource ds = new ComboPooledDataSource();
        try {
            ds.setDriverClass(driver);
            ds.setJdbcUrl(url);
            ds.setUser(username);
            ds.setPassword(password);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return ds;
    }
}

jdbcConfig.properties

jdbc.driver=com.mysql.cj.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/spring?serverTimezone=UTC
jdbc.username=root
jdbc.password=582211

Spring整合Junit

在这里插入图片描述

运行报错在这里插入图片描述

  1. 导入spring继承junit的包
    坐标
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-test</artifactId>
    <version>5.3.14</version>
</dependency>
  1. 使用junit提供的一个注解把原有的main方法替换了替换成spring提供的
    @RunWith
    在这里插入图片描述
  2. 告知spring的运行器,spring和IOC创建是基于xml还是注解,并且说明位置
    @ContextConfiguration
    locations:指定xml文件的位置,加上classpath关键字,表示文件在类路径下
    classes:指定注解类所在位置
    不能两个都写
    当使用spring 5.x版本的时候,必须使用junit4.12及以上的版本

测试类

import config.SpringConfiguration;
import itheima2.domain.IAccount;
import itheima2.service.IAccountService;
import itheima2.service.impl.IAccountServiceImpl;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

import java.util.List;

/**
 * 单元测试
 */
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = {"classpath:bean.xml"})
public class AccountServiceTest {
    private ApplicationContext ac2;
    @Autowired
    @Qualifier("accountService")
    private IAccountService service;
    private IAccountService as2;

    /**
     * 可删除
     */
    @Before
    public void init() {
        ac2 = new AnnotationConfigApplicationContext(SpringConfiguration.class);
        as2 = ac2.getBean("Annotation_accountService", IAccountServiceImpl.class);
    }

    @Test
    public void testFindAll() {
        //2.执行方法
        List<IAccount> list = service.findAllAccount();
        for (IAccount acc : list) {
            System.out.println(acc);
        }
    }

    @Test
    public void testFindAll2() {
        List<IAccount> list = as2.findAllAccount();
        for (IAccount acc : list) {
            System.out.println(acc);
        }
    }

    @Test
    public void testFindOne() {
        IAccount account = service.findAccountById(2);
        System.out.println(account);
    }

    @Test
    public void testSave() {
        IAccount account = new IAccount();
        account.setName("李世礴");
        account.setMoney(10000.26f);
        service.saveAccount(account);
    }

    @Test
    public void testUpdate() {
        IAccount account = service.findAccountById(3);
        account.setName("Pedestrian");
        service.updateAccount(account);
    }

    @Test
    public void testDelete() {
        service.deleteAccount(1);
    }
}

完善之前的案例(引申出AOP)

今日内容
在这里插入图片描述
IAccountServiceImpl

package itheima.service.impl;

import itheima.dao.IAccountDao;
import itheima.domain.IAccount;
import itheima.service.IAccountService;
import itheima.utils.TransactionManager;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.util.List;

@Service("accountService")
/**
 * 事务的控制应该在业务层
 */
public class IAccountServiceImpl implements IAccountService {
    @Autowired
    private IAccountDao accountDao;
    private TransactionManager transactionManager;

    public void setTransactionManager(TransactionManager transactionManager) {
        this.transactionManager = transactionManager;
    }

    public void setAccountDao(IAccountDao accountDao) {
        this.accountDao = accountDao;
    }

    @Override
    public List<IAccount> findAllAccount() {

        try {
            transactionManager.beginTransaction();
            List<IAccount> list = accountDao.findAllAccount();
            transactionManager.commit();
            return list;
        } catch (Exception e) {
            transactionManager.rollBack();
        }finally{
            transactionManager.release();
        }
        return null;
    }

    @Override
    public IAccount findAccountById(Integer id) {
        try {
            transactionManager.beginTransaction();
            IAccount iac = accountDao.findAccountById(id);
            transactionManager.commit();
            return iac;
        } catch (Exception e) {
            transactionManager.rollBack();
        }finally{
            transactionManager.release();
        }
        return null;
    }

    @Override
    public void saveAccount(IAccount account) {
        try {
            transactionManager.beginTransaction();
            accountDao.saveAccount(account);
            transactionManager.commit();
        } catch (Exception e) {
            transactionManager.rollBack();
        }finally{
            transactionManager.release();
        }

    }

    @Override
    public void updateAccount(IAccount account) {
        try {
            transactionManager.beginTransaction();
            accountDao.updateAccount(account);
            transactionManager.commit();
        } catch (Exception e) {
            transactionManager.rollBack();
        }finally{
            transactionManager.release();
        }
    }

    @Override
    public void deleteAccount(Integer accountId) {
        try {
            transactionManager.beginTransaction();
            accountDao.deleteAccount(accountId);
            transactionManager.commit();
        } catch (Exception e) {
            transactionManager.rollBack();
        }finally{
            transactionManager.release();
        }

    }

    @Override
    public void transfer(String sourceName, String targetName, Float money) {
        try {
            transactionManager.beginTransaction();
            //1.根据名称查询转出账户
            IAccount source = accountDao.findAccountByName(sourceName);
            //2.根据名称查询转入账户
            IAccount target = accountDao.findAccountByName(targetName);
            //3.转出账户减钱
            source.setMoney(source.getMoney()-money);
            //4.转入账户加钱
            target.setMoney(target.getMoney()+money);
            //5.更新转出账户
            accountDao.updateAccount(source);
            int i = 3/0;
            accountDao.updateAccount(target);
            //6.更新转入账户
            transactionManager.commit();
        } catch (Exception e) {
            transactionManager.rollBack();
        }finally{
            transactionManager.release();
        }
    }
}

通过对业务层改造,已经可以实现事务控制了,但是由于我们添加了事务控制,也产生了一
个新的问题:
业务层方法变得臃肿了,里面充斥着很多重复代码。并且业务层方法和事务控制方法耦合了。
试想一下,如果我们此时提交,回滚,释放资源中任何一个方法名变更,都需要修改业务层的代码,况且这还只是一个业务层实现类,而实际的项目中这种业务层实现类可能有十几个甚至几十个。

IAccountDaoImpl

package itheima.dao.impl;

import itheima.dao.IAccountDao;
import itheima.domain.IAccount;
import itheima.utils.ConnectionUtils;
import org.apache.commons.dbutils.QueryRunner;
import org.apache.commons.dbutils.handlers.BeanHandler;
import org.apache.commons.dbutils.handlers.BeanListHandler;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Repository;

import java.util.List;

@Repository

public class IAccountDaoImpl implements IAccountDao {
    @Autowired
    @Qualifier("runner")
    private QueryRunner runner;
    private ConnectionUtils connectionUtils;

    public void setConnectionUtils(ConnectionUtils connectionUtils) {
        this.connectionUtils = connectionUtils;
    }

    public void setRunner(QueryRunner runner) {
        this.runner = runner;
    }

    @Override
    public IAccount findAccountByName(String accountName) {
        try{
            List<IAccount> list = runner.query(connectionUtils.getThreadConnection(),"select * from account where name =  ?",new BeanListHandler<>(IAccount.class),accountName);
            if(list == null || list.size() == 0){
                return null;
            }
            if(list.size() > 1){
                throw new RuntimeException("结果集不唯一,数据有问题");
            }
            return list.get(0);
        }catch(Exception e){
            throw new RuntimeException(e);
        }
    }

    @Override
    public List<IAccount> findAllAccount() {
        try{
            return runner.query(connectionUtils.getThreadConnection(),"select * from account",new BeanListHandler<>(IAccount.class));
        }catch(Exception e){
            throw new RuntimeException(e);
        }
    }

    @Override
    public IAccount findAccountById(int id) {
        try{
            return runner.query(connectionUtils.getThreadConnection(),"select * from account where id = ?",new BeanHandler<>(IAccount.class),id);
        }catch(Exception e){
            throw new RuntimeException(e);
        }
    }

    @Override
    public void saveAccount(IAccount account) {
        try{
            runner.update(connectionUtils.getThreadConnection(),"insert into account(name,money) values(?,?)",account.getName(),account.getMoney());
        }catch(Exception e){
            throw new RuntimeException(e);
        }
    }

    @Override
    public void updateAccount(IAccount a) {
        try{
            runner.update(connectionUtils.getThreadConnection(),"update account set name = ?,money = ? where id = ?",a.getName(),a.getMoney(),a.getId());
        }catch(Exception e){
            throw new RuntimeException(e);
        }
    }

    @Override
    public void deleteAccount(Integer accountId) {
        try{
            runner.update(connectionUtils.getThreadConnection(),"delete from account where id = ?",accountId);
        }catch(Exception e){
            throw new RuntimeException(e);
        }
    }
}

但如果发生异常,并且因为QueryRunner是多例的,所以每次都会创建一个Connection对象,不同的数据库操作时机,这会造成线程问题,所以我们需要使用线程类加以控制Connection对象
在这里插入图片描述
创建utils包,创建ConnectionUtils类和TransactionManager类
ConnectionUtils

package itheima.utils;

import javax.sql.DataSource;
import java.sql.Connection;

/**
 * 连接的工具类,用于从数据源中获取一个连接,实现和线程的绑定
 */
public class ConnectionUtils {
    private ThreadLocal<Connection> tl = new ThreadLocal<>();

    private DataSource dataSource;

    public void setDataSource(DataSource dataSource) {
        this.dataSource = dataSource;
    }

    /**
     * 获取当前线程上的连接
     * @return
     */
    public Connection getThreadConnection(){
        //先从ThreadLocal获取
        Connection conn = tl.get();
        //判断当前线程上是否有连接
        try{
            if(conn == null){
                //从数据源获取一个连接,并且存入线程中
                conn = dataSource.getConnection();
                tl.set(conn);
            }
            return conn;
        }catch(Exception e){
            throw new RuntimeException(e);
        }
    }

    /**
     * 把连接和线程解绑
     */
    public void removeConnection(){

    }
}

TransactionManager

package itheima.utils;

import java.sql.SQLException;

/**
 * 事务管理工具类,包含开启事务,提交事务,回滚事务和释放连接
 */
public class TransactionManager {
    private ConnectionUtils connectionUtils;

    public void setConnectionUtils(ConnectionUtils connectionUtils) {
        this.connectionUtils = connectionUtils;
    }

    /**
     * 开启事务
     */
    public void beginTransaction(){
        try {
            connectionUtils.getThreadConnection().setAutoCommit(false);
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }
    /**
     * 提交事务
     */
    public void commit(){
        try {
            connectionUtils.getThreadConnection().commit();
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }
    /**
     * 回滚事务
     */
    public void rollBack(){
        try {
            connectionUtils.getThreadConnection().rollback();
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }
    /**
     * 释放连接
     */
    public void release(){
        try {
//            归还连接池
            connectionUtils.removeConnection();
            connectionUtils.getThreadConnection().close();
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }
}

动态代理

特点:字节码随用随创建,随用随加载。
它与静态代理的区别也在于此。因为静态代理是字节码一上来就创建好,并完成加载。
作用:不修改源码的基础上对方法增强
装饰者模式就是静态代理的一种体现。
在这里插入图片描述
动态代理常用的有两种方式
首先给我Producer类

package itheima.proxy;

public class Producer implements IProducer{
    public Producer() {
    }
    public void saleProducts(double money){
        System.out.println("销售产品,并赚差价:"+money);
    }
    public void afterService(double money){
        System.out.println("提供售后服务,并赚钱:"+money);
    }
}

  • 基于接口的动态代理
    提供者:JDK 官方的 Proxy 类。
    如何使用:使用Proxy类中的newProxyInstance方法
    要求:被代理类至少实现一个接口,否则不能使用
    newProxyInstance方法的参数:
    ClassLoader: 类加载器,用于加载代理对象字节码的,和被代理对象使用相同的类加载器 固定写法
    Class[]: 字节码数组,用于让代理对象和被代理对象有相同方法 固定写法
    InvocationHandler: 用于提供增强代码,让我们如何写代理,一般写该接口的实现类的通常都为匿名内部类,但不是必须的
    在这里插入图片描述

  • 基于子类的动态代理
    提供者:第三方的 CGLib库,如果报 asmxxxx 异常,需要导入 asm.jar。
    要求:被代理类不能用 final 修饰的类(最终类)。
    如何使用:使用Enhancer类中的create方法
    create方法参数:
    Class: 字节码
    用于指定被代理对象的字节码
    Callback: 用于提供增强代码
    我们一般写的都是该接口的子实现类:MethodInterceptor
    导入坐标

		<dependency>
            <groupId>cglib</groupId>
            <artifactId>cglib</artifactId>
            <version>2.2.2</version>
        </dependency>

Producer2类
在这里插入图片描述
Client2类
在这里插入图片描述

增强 IAccountService

通过对业务层改造,可以实现事务控制了,但是由于我们添加了事务控制,也产生了一
个新的问题:
业务层方法变得臃肿了,里面充斥着很多重复代码。并且业务层方法和事务控制方法耦合了。
试想一下,如果我们此时提交,回滚,释放资源中任何一个方法名变更,都需要修改业务层的代码,况且这还只是一个业务层实现类,而实际的项目中这种业务层实现类可能有十几个甚至几十个。
IAccountServiceImpl

package itheima.service.impl;

import itheima.dao.IAccountDao;
import itheima.domain.IAccount;
import itheima.service.IAccountService;
import itheima.utils.TransactionManager;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.util.List;

@Service("accountService")
/**
 * 事务的控制应该在业务层
 */
public class IAccountServiceImpl implements IAccountService {
    @Autowired
    private IAccountDao accountDao;
    private TransactionManager transactionManager;

    public void setTransactionManager(TransactionManager transactionManager) {
        this.transactionManager = transactionManager;
    }

    public void setAccountDao(IAccountDao accountDao) {
        this.accountDao = accountDao;
    }

    @Override
    public List<IAccount> findAllAccount() {
        List<IAccount> list = accountDao.findAllAccount();
        return list;
    }

    @Override
    public IAccount findAccountById(Integer id) {

        IAccount iac = accountDao.findAccountById(id);

        return iac;
    }

    @Override
    public void saveAccount(IAccount account) {
        accountDao.saveAccount(account);
    }

    @Override
    public void updateAccount(IAccount account) {
        accountDao.updateAccount(account);
    }

    @Override
    public void deleteAccount(Integer accountId) {
        accountDao.deleteAccount(accountId);
    }

    @Override
    public void transfer(String sourceName, String targetName, Float money) {
        System.out.println("Begin transfer...");
        //1.根据名称查询转出账户
        IAccount source = accountDao.findAccountByName(sourceName);
        //2.根据名称查询转入账户
        IAccount target = accountDao.findAccountByName(targetName);
        //3.转出账户减钱
        source.setMoney(source.getMoney() - money);
        //4.转入账户加钱
        target.setMoney(target.getMoney() + money);
        //5.更新转出账户
        accountDao.updateAccount(source);
        int i = 3 / 0;
        //6.更新转入账户
        accountDao.updateAccount(target);
    }
}

创建工厂类BeanFactory

package itheima.factory;

import itheima.service.IAccountService;
import itheima.utils.TransactionManager;

import java.lang.reflect.Proxy;

public class BeanFactory {
    private IAccountService accountService;
    private TransactionManager txManager;

    public void setTxManager(TransactionManager txManager) {
        this.txManager = txManager;
    }

    public final void setAccountService(IAccountService accountService) {
        this.accountService = accountService;
    }
    /**
     * 获得Service代理对象
     */
    public IAccountService getAccountService(){
        return (IAccountService)Proxy.newProxyInstance(accountService.getClass().getClassLoader(),
               accountService.getClass().getInterfaces(),
                (proxy, method, args) -> {
                    Object obj = null;
                    try {
                        txManager.beginTransaction();
                        obj = method.invoke(accountService,args);
                        txManager.commit();
                        return obj;
                    } catch (Exception e) {
                       txManager.rollBack();
                       throw new RuntimeException(e);
                    }finally{
                        txManager.release();
                    }
                });
    }
}

相应的修改bean.xml

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/context
        http://www.springframework.org/schema/context/spring-context.xsd">
<!--    告知spring在创建容器时,使用注解时要扫描的包
        配置所需要的标签不在beans的约束中
        而是context名称空间和约束-->
    <context:component-scan base-package="itheima"/>

<!--    配置代理后的service-->
    <bean id="proxyAccountService" factory-bean="beanFactory" factory-method="getAccountService"/>

    <bean id="beanFactory" class="itheima.factory.BeanFactory">
        <property name="accountService" ref="accountService"></property>
        <property name="txManager" ref="txManger"></property>
    </bean>

    <!--    配置Service-->
    <bean id="accountService" class="itheima.service.impl.IAccountServiceImpl">
        <property name="accountDao" ref="accountDao"/>
    </bean>

    <!--    配置Dao对象-->
    <bean id="accountDao" class="itheima.dao.impl.IAccountDaoImpl">
        <!--        注入QueryRunner-->
        <property name="runner" ref="runner"/>
        <property name="connectionUtils" ref="connectionUtils"/>
    </bean>

    <!--    配置QueryRunner 防止线程干扰 使用多例-->
    <bean id="runner" class="org.apache.commons.dbutils.QueryRunner" scope="prototype">
<!--        注入数据源 但是没有set方法 使用构造函数方法-->
        <constructor-arg name="ds" ref="dataSource"/>
    </bean>

<!--    配置数据源-->
    <bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
<!--        连接数据库的必备信息-->
<!--        数据库版本是8.* 否则value为"com.mysql.jdbc.Driver"-->
        <property name="driverClass" value="com.mysql.cj.jdbc.Driver"/>
<!--        由于时差问题和版本问题 一定要加serverTimezone=UTC-->
        <property name="jdbcUrl" value="jdbc:mysql://localhost:3306/spring?serverTimezone=UTC"/>
        <property name="user" value="root"/>
        <property name="password" value="582211"/>
    </bean>
    <bean id="connectionUtils" class="itheima.utils.ConnectionUtils">
        <property name="dataSource" ref="dataSource"/>
    </bean>
    <bean id="txManger" class="itheima.utils.TransactionManager">
        <property name="connectionUtils" ref="connectionUtils"/>
    </bean>
</beans>

可以实现事务回滚,即如果在存取钱的过程中出现错误,先开启事务后,可以将执行过的操作回滚。
于是我们使用了动态代理技术

正式介绍AOP(面向切面编程 用于增强方法)

在这里插入图片描述
作用及优势
在这里插入图片描述
实现方式
在这里插入图片描述
我们学习 spring 的 aop,就是通过配置的方式,实现上一章节的功能

AOP专用术语

在这里插入图片描述
连接点:接口中的方法全是连接点,连接着类和增强方法
切入点:被增强的方法,可能有些方法没有被增强,那么他就不是切入点
在这里插入图片描述
Advice通知:拦截了连接点之后,所要进行的操作,在上面的例子中,TransactionManager类所做的事情就是通知,进行事务的控制
在这里插入图片描述
在这里插入图片描述
Target目标对象:例子中为
private IAccountService accountService;此对象
Weaving(织入):
代理对象加入事务控制的这个过程叫Weaving
Proxy(代理):
getAccountService返回的增强对象就是Proxy
Aspect(切面):
切入点:被增强的方法
通知:提供了公共代码的类,如TransactionManager
方法何时执行,对应顺序与操作,这一过程就是切面
如开启事务要在最开始,提交要在invoke后面…
在这里插入图片描述

注意为何事务回滚失败

mysql事务回滚无效是因为数据库引擎设置为“MyISAM”了,该引擎是不支持事务回滚的,所以产生事务回滚无效,其解决办法就是将引擎更换为“InnoDB”即可恢复正常。
改单个表

ALTER TABLE TABLENAME ENGINE=InnoDB;
ALTER TABLE TABLENAME ENGINE=MyISAM;
改多个表

复制代码
#修改为InnoDB

SELECT CONCAT( 'ALTER TABLE ', TABLE_NAME, ' ENGINE=InnoDB;' )
FROM information_schema.tables
WHERE table_schema = 'DBNAME'
LIMIT 0 , 10000;

#修改为MyISAM

SELECT CONCAT( 'ALTER TABLE ', TABLE_NAME, ' ENGINE=MyISAM;' )
FROM information_schema.tables
WHERE table_schema = 'DBNAME'
LIMIT 0 , 10000;

复制代码
使用说明:

  1. 将以上SQL语句中的 DBNAME 替换成需要修改的数据库名称。

  2. 执行SQL,这个时候还没有修改,只是给了一个查询结果。

  3. 将查询结果复制,去掉前后的引号,然后执行。
    在这里插入图片描述

基于 XML 的 AOP 配置

AOP需要导入的坐标

<!--        spring-->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>5.0.20.RELEASE</version>
        </dependency>
<!--        aop-->
        <dependency>
            <groupId>org.aspectj</groupId>
            <artifactId>aspectjweaver</artifactId>
            <version>1.8.7</version>
        </dependency>

aop的bean.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:aop="http://www.springframework.org/schema/aop"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/aop
        http://www.springframework.org/schema/aop/spring-aop.xsd">
</bean>

在这里插入图片描述
在这里插入图片描述

例子
在这里插入图片描述
Logger

package utils;


import org.aspectj.lang.ProceedingJoinPoint;

/**
 * 用于记录日志的工具类 提供了公共代码
 */
public class Logger {
    /**
     * 打印日志,让其在切入点方法(业务层方法)执行之前执行
     */

    public void beforePrintLog(){
        System.out.println(Logger.class.getName()+"类中的前置通知方法开始执行了");
    }
    /**
     * 后置通知
     */
    public void afterPrintLog(){
        System.out.println(Logger.class.getName()+"类中的后置通知方法开始执行了");
    }

    /**
     * 最终通知
     */
    public void finalPrintLog(){
        System.out.println(Logger.class.getName()+"类中的最终通知方法开始执行了");
    }

    /**
     * 异常通知
     */
    public void exceptionPrintLog(){
        System.out.println(Logger.class.getName()+"类中的异常通知方法开始执行了");
    }
    /**
     * 环绕通知
     */
    public Object aroundPrintLog(ProceedingJoinPoint pjp){
        Object rtValue=null;
        try {
            Object[] args = pjp.getArgs();
            //代码写在前面表示前置通知
            System.out.println(Logger.class.getName()+"类中的环绕通知方法开始执行了");
            rtValue = pjp.proceed(args);
            //代码写在后面表示后置通知
            return rtValue;
        } catch (Throwable e) {
            //代码写在异常里表示异常通知
            throw new RuntimeException(e);
        }finally{
//            写在finally表示最终通知
        }
    }
}

IAccountServiceImpl

package service.impl;

import service.IAccountService;

public class IAccountServiceImpl implements IAccountService {
    @Override
    public void saveAccount() {
        System.out.println("执行保存");
    }

    @Override
    public void updateAccount(int i) {
        System.out.println("执行更新"+i);
    }

    @Override
    public int deleteAccount() {
        System.out.println("执行删除");
        return 0;
    }
}

bean.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:aop="http://www.springframework.org/schema/aop"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/aop
        http://www.springframework.org/schema/aop/spring-aop.xsd">

    <bean id="accountService" class="service.impl.IAccountServiceImpl"/>
    <bean id="logger" class="utils.Logger"/>

<!--    配置AOP-->
    <aop:config>
<!--        配置切面-->
        <aop:aspect id="logAdvice" ref="logger">
<!--            配置通知类型并且建立通知方法和切入点方法的关联-->
            <aop:before method="printLog" pointcut="execution( public void service.impl.IAccountServiceImpl.saveAccount() )"></aop:before>
        </aop:aspect>
    </aop:config>
</beans>

Test

package test;

import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import service.IAccountService;

public class Test {
    public static void main(String[] args) {
        ApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml");
        IAccountService accountService = ac.getBean("accountService", IAccountService.class);
        accountService.saveAccount();
    }
}

在这里插入图片描述

四种常用通知类型及切入点表达式
<!--    配置AOP-->
    <aop:config>
<!--        配置切面-->
        <aop:aspect id="logAdvice" ref="logger">
<!--            配置通知类型并且建立通知方法和切入点方法的关联-->
            <!--            前置通知-->
            <aop:before method="beforePrintLog" pointcut="execution( * service.impl.*.*(..) )"/>
            <!--            后置通知-->
            <aop:after-returning method="afterPrintLog" pointcut="execution( * service.impl.*.*(..) )"/>
            <!--            异常通知-->
            <aop:after-throwing method="exceptionPrintLog" pointcut-ref="pt1" />
            <!--            最终通知-->
            <aop:after method="finalPrintLog" pointcut-ref="pt1" />
            
<!--            配置切入点表达式 放在aspect只能在内部使用 如果想放到外面必须放到所有aspect标签的前面-->
            <aop:pointcut id="pt1" expression="execution(* service.impl.*.*(..))"/>
        </aop:aspect>
    </aop:config>
环绕通知

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

public Object aroundPrintLog(ProceedingJoinPoint pjp){
        Object rtValue=null;
        try {
            Object[] args = pjp.getArgs();
            //代码写在前面表示前置通知
            System.out.println(Logger.class.getName()+"类中的环绕通知方法开始执行了");
            rtValue = pjp.proceed(args);
            //代码写在后面表示后置通知
            return rtValue;
        } catch (Throwable e) {
            //代码写在异常里表示异常通知
            throw new RuntimeException(e);
        }finally{
//            写在finally表示最终通知
        }
    }
基于 注解 的 AOP 配置

在这里插入图片描述

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

JDBCTemplate(访问数据库的工具)

在这里插入图片描述
在这里插入图片描述
导包

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

        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-jdbc</artifactId>
            <version>5.2.9.RELEASE</version>
        </dependency>

        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>8.0.21</version>
        </dependency>
    </dependencies>

bean配置

<!--    配置jdbctemplate-->
    <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
        <property name="dataSource" ref="dataSource"/>
    </bean>
    <bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
        <property name="driverClassName" value="com.mysql.cj.jdbc.Driver"/>
        <property name="url" value="jdbc:mysql://localhost:3306/spring?serverTimezone=UTC"/>
        <property name="password" value="582211"/>
        <property name="username" value="root"/>
    </bean>
对数据库的操作

JdbcTemplate jt = (JdbcTemplate) ac.getBean("jdbcTemplate");
保存
jt.update("insert into account(name,money)values(?,?)","fff",5000);
更新
jt.update("update account set money = money-? where id = ?",300,6);
删除
jt.update("delete from account where id = ?",6);
查询
List<Account> accounts = jt.query("select * from account where money > ? ", new AccountRowMapper(), 500);
使用Spring内置的RowMapper
在这里插入图片描述
queryForObject(String,new BeanRowMapper<>(xx.class),?号参数,?参数,…)

jdbc.query("select * from account where money > ?",new BeanPropertyRowMapper<Account>(Account.class),10000f);
如果返回为基本数据类型,如Integer,则直接传入Integer.class,不用传入new BeanPropertyRowMapper<>(xx.class)
自定义RowMapper类型

class AccountRowMapper implements RowMapper<Account> {
    @Override
    public Account mapRow(ResultSet resultSet, int i) throws SQLException {
        Account account = new Account();
        account.setId(resultSet.getInt("id"));
        account.setName(resultSet.getString("name"));
        account.setMoney(resultSet.getInt("money"));
        return account;
    }
}

jdbc.query("select * from account where money > ?",new AccountRowMapper(),10000f);
当我们把jdbcTemplate放到Dao中的时候,可能会有多种不同的Dao,导致会存在很多重复且繁琐的代码,这该怎么解决?

private JdbcTemplate jdbcTemplate;
public void setJdbcTemplate(JdbcTemplate jdbcTemplate) {
	this.jdbcTemplate = jdbcTemplate;
}

首先写一个JdbcDao类

package dao.impl;

import org.springframework.jdbc.core.JdbcTemplate;

import javax.sql.DataSource;

public class JdbcDaoSupport {
    private JdbcTemplate jdbc;

    public void setJdbc(JdbcTemplate jdbc) {
        this.jdbc = jdbc;
    }

    public JdbcTemplate getJdbc() {
        return jdbc;
    }

    public void setDataSource(DataSource dataSource) {
        if(jdbc==null){
            jdbc = createJdbcTemplate(dataSource);
        }
    }
    public JdbcTemplate createJdbcTemplate(DataSource dataSource){
        return new JdbcTemplate(dataSource);
    }
}

让Dao实现类继承它
public class AccountDaoImpl extends JdbcDaoSupport implements AccountDao
这时我们只需要通过spring的ioc来注入Datasource,因为继承了其父类,所以也存在setDataSource方法,其中的JdbcTemplate会实现初始化,这样就解决了重复代码的问题

	<bean id="accountDao" class="dao.impl.AccountDaoImpl">
        <property name="dataSource" ref="dataSource"/>
    </bean>

AccountDaoImpl

package dao.impl;

import dao.AccountDao;
import org.springframework.jdbc.core.BeanPropertyRowMapper;
import domain.Account;
import java.util.List;

public class AccountDaoImpl extends JdbcDaoSupport implements AccountDao {
    @Override
    public Account findAccountById(Integer id) {
        List<Account> list = getJdbc().query("select * from account where id = ?", new BeanPropertyRowMapper<>(Account.class), id);
        if(list.isEmpty()){
            System.out.println("未查找到数据!");
        }
        if(list.size()>1){
            throw new RuntimeException("结果集不唯一!");
        }
        return list.get(0);
    }

    @Override
    public List<Account> findAccountByName(String name) {
        List<Account> list = getJdbc().query("select * from account where name = ?", new BeanPropertyRowMapper<>(Account.class), name);
        if(list.isEmpty()){
            System.out.println("未查找到数据!");
        }
        return list;
    }
}

Spring 中的事务控制(回滚以及提交等)

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

基于 XML 的声明式事务控制(配置方式)

spring中基于XML的声明式事务控制配置步骤
在这里插入图片描述

<!--    配置jdbctemplate-->
    <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
        <property name="dataSource" ref="dataSource"/>
    </bean>
    <bean id="accountDao" class="dao.impl.AccountDaoImpl">
        <property name="dataSource" ref="dataSource"/>
    </bean>
    <bean id="accountService" class="service.impl.IAccountServiceImpl">
        <property name="accountDao" ref="accountDao"/>
    </bean>
    <bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
        <property name="driverClassName" value="com.mysql.cj.jdbc.Driver"/>
        <property name="url" value="jdbc:mysql://localhost:3306/spring?serverTimezone=UTC"/>
        <property name="password" value="582211"/>
        <property name="username" value="root"/>
    </bean>
<!-- 配置事务管理器 -->
    <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <property name="dataSource" ref="dataSource"></property>
    </bean>

    <!-- 配置事务的通知-->
    <tx:advice id="txAdvice" transaction-manager="transactionManager">
        <!-- 配置事务的属性
                isolation:用于指定事务的隔离级别。默认值是DEFAULT,表示使用数据库的默认隔离级别。
                propagation:用于指定事务的传播行为。默认值是REQUIRED,表示一定会有事务,增删改的选择。查询方法可以选择SUPPORTS。
                read-only:用于指定事务是否只读。只有查询方法才能设置为true。默认值是false,表示读写。
                timeout:用于指定事务的超时时间,默认值是-1,表示永不超时。如果指定了数值,以秒为单位。
                rollback-for:用于指定一个异常,当产生该异常时,事务回滚,产生其他异常时,事务不回滚。没有默认值,表示任何异常都回滚。
                no-rollback-for:用于指定一个异常,当产生该异常时,事务不回滚,产生其他异常时事务回滚。没有默认值,表示任何异常都回滚。
        -->
        <!--method标签里	name为方法名称-->
        <tx:attributes>
            <tx:method name="*" propagation="REQUIRED" read-only="false"/>
            <tx:method name="find*" propagation="SUPPORTS" read-only="true"/>
        </tx:attributes>
    </tx:advice>

    <!-- 配置aop-->
    <aop:config>
        <!-- 配置切入点表达式-->
        <aop:pointcut id="pt1" expression="execution(* service.impl.*.*(..))"></aop:pointcut>
        <!--建立切入点表达式和事务通知的对应关系 -->
        <aop:advisor advice-ref="txAdvice" pointcut-ref="pt1"></aop:advisor>
    </aop:config>
基于注解的配置方式(了解)

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
该注解的属性和 xml 中的属性含义一致。该注解可以出现在接口上,类上和方法上。
出现接口上,表示该接口的所有实现类都有事务支持。
出现在类上,表示类中所有方法有事务支持
出现在方法上,表示方法有事务支持。
以上三个位置的优先级:方法>类>接口
在这里插入图片描述
在这里插入图片描述

spring基于编程式事务控制(了解)

bean.xml与xml配置一致,只不过不需要配置事务通知
再Service层的实现类IAccountServiceImpl添加对象
private TransactionTemplate transactionTemplate;
其内部有一个方法
在这里插入图片描述
TransactionCallback可以自己实现其内部方法,即匿名内部类
在这里插入图片描述

	<bean id="transactionTemplate" class="org.springframework.transaction.support.TransactionTemplate">
        <property name="transactionManager" ref="transactionManager"/>
    </bean>

非常不建议这样编写,因为代码又会越来越冗余

Spring5新特性

在这里插入图片描述
第一:基于 JDK8 的反射增强
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

SpringMVC(表现层框架)

创建项目跳过联网下载的步骤
在这里插入图片描述
加一组蓝色部分的属性

基本概念

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

SpringMVC 是一种基于 Java 的实现 MVC 设计模型的请求驱动类型的轻量级 Web 框架,属于 Spring FrameWork 的后续产品,已经融合在 Spring Web Flow 里面。Spring 框架提供了构建 Web 应用程序的全功能 MVC 模块。使用 Spring 可插入的 MVC 架构,从而在使用 Spring 进行 WEB 开发时,可以选择使用 Spring的 Spring MVC 框架或集成其他 MVC 开发框架,如 Struts1(现在一般不用),Struts2 等。
SpringMVC 已经成为目前最主流的 MVC 框架之一,并且随着 Spring3.0 的发布,全面超越 Struts2,成为最优秀的 MVC 框架。
它通过一套注解,让一个简单的 Java 类成为处理请求的控制器,而无须实现任何接口。同时它还支持RESTful 编程风格的请求。

优势

在这里插入图片描述
数据每走一步,都有相关的模块来控制它
在这里插入图片描述

入门程序步骤

在这里插入图片描述
创建web工程在这里插入图片描述
导入maven坐标

<!-- 版本锁定 -->
<properties>
	<spring.version>5.0.2.RELEASE</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-web</artifactId>
      <version>${spring.version}</version>
    </dependency>
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-webmvc</artifactId>
      <version>${spring.version}</version>
    </dependency>
    <dependency>
      <groupId>javax.servlet</groupId>
      <artifactId>servlet-api</artifactId>
      <version>2.5</version>
      <scope>provided</scope>
    </dependency>
    <dependency>
      <groupId>javax.servlet.jsp</groupId>
      <artifactId>jsp-api</artifactId>
      <version>2.0</version>
      <scope>provided</scope>
    </dependency>
  </dependencies>

目录结构如下:
在这里插入图片描述

配置核心的控制器(配置DispatcherServlet)
web.xml如下

<!DOCTYPE web-app PUBLIC
 "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
 "http://java.sun.com/dtd/web-app_2_3.dtd" >

<web-app>
  <display-name>Archetype Created Web Application</display-name>
  
  <servlet>
    <servlet-name>dispatcherServlet</servlet-name>
    <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<!--    初始化扫描springmvc配置文件-->
    <init-param>
      <param-name>contextConfigLocation</param-name>
      <param-value>classpath:spring.xml</param-value>
    </init-param>
<!--    表示启动服务器就创建此servlet-->
    <load-on-startup>1</load-on-startup>
  </servlet>
  <servlet-mapping>
    <servlet-name>dispatcherServlet</servlet-name>
    任意访问servlet的操作均要经过dispatcherServlet
    <url-pattern>/</url-pattern>
  </servlet-mapping>
</web-app>

在WEB-INF目录下创建pages文件夹,存放跳转的文件
里面存放success.jsp
在这里插入图片描述
在index.jsp配置超链接
在这里插入图片描述
创建JspController

package controller;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;

@Controller
public class JspController {
    @RequestMapping(path="/hello")
    public String saySomething(){
        System.out.println("Hello StringMVC");
        return "success";
    }
}

@RequestMapping表示方法映射到的访问链接
函数字符串返回值表示跳转到的页面的文件名称

spring.xml

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


<!--        开启注解扫描-->
    <context:component-scan base-package="controller"/>
<!--    配置视图解析器对象-->
<!--    用于访问某个java类后,根据其方法返回的字符串名称,跳转到指定的页面 如success.jsp-->
    <bean id="internalResourceViewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<!--        表明跳转页面的文件所在的目录-->
        <property name="prefix" value="/WEB-INF/pages/"/>

<!--        表明文件的后缀名-->
        <property name="suffix" value=".jsp"/>
    </bean>

<!--    开启springmvc框架注解的支持-->
    <mvc:annotation-driven/>
</beans>

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

入门案例的执行过程分析

在这里插入图片描述
SpringMVC官方提供图形
在这里插入图片描述
1、服务器启动,应用被加载。读取到 web.xml 中的配置创建 spring 容器并且初始化容器中的对象。
从入门案例中可以看到的是:HelloController 和 InternalResourceViewResolver,但是远不
止这些。
2、浏览器发送请求,被 DispatherServlet 捕获,该 Servlet 并不处理请求,而是把请求转发出去。转发的路径是根据请求 URL,匹配@RequestMapping 中的内容。
3、匹配到了后,执行对应方法。该方法有一个返回值。
4、根据方法的返回值,借助 InternalResourceViewResolver 找到对应的结果视图。
5、渲染结果视图,响应浏览器。

组件说明

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
mvc:annotation-driven说明:
在 SpringMVC 的各个组件中,处理器映射器、处理器适配器、视图解析器称为 SpringMVC 的三大组件。
使 用 mvc:annotation-driven 自动加载 RequestMappingHandlerMapping (处理映射器) 和
RequestMappingHandlerAdapter ( 处 理 适 配 器 ) , 可 用 在 SpringMVC.xml 配 置 文 件 中 使 用
mvc:annotation-driven替代注解处理器和适配器的配置。
它就相当于在 xml 中配置了:
在这里插入图片描述
在这里插入图片描述

RequestMapping 注解

在这里插入图片描述
在这里插入图片描述
params
在这里插入图片描述
params={“username=haha”} 表示请求参数里必须有username且必须为haha
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

请求参数的绑定

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
pojo类型=javabean
在这里插入图片描述
在这里插入图片描述

在这里插入图片描述
在这里插入图片描述

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

乱码问题

在这里插入图片描述
在这里插入图片描述

自定义类型转换器

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

使用 ServletAPI 对象作为方法参数

在这里插入图片描述
在这里插入图片描述

常用注解

RequestParam

在这里插入图片描述
在这里插入图片描述

RequestBody

在这里插入图片描述
在这里插入图片描述

PathVaribale

在这里插入图片描述
在这里插入图片描述
Restful风格
根据请求类型来执行相应方法
如果请求类型一致,可以填写占位符来执行相应方法
在这里插入图片描述

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

HiddentHttpMethodFilter

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

RequestHeader

在这里插入图片描述
在这里插入图片描述

CookieValue

在这里插入图片描述
在这里插入图片描述

ModelAttribute

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
存进map和取出
在这里插入图片描述
在这里插入图片描述

SessionAttribute

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
可以在jsp页面通过 ${requestScope.} 和 ${sessionScope.} 调用存入的数据

响应数据和结果视图(参数传递)

注:如想将参数传递至jsp中,想要使用el表达式,开头为:
<%@ page contentType="text/html;charset=UTF-8" language="java" isELIgnored="false" %>

返回值分类

在这里插入图片描述
在这里插入图片描述
返回值为void,会默认访问@RequestMapping里的 请求路径.jsp
在这里插入图片描述
导入坐标

<dependency>
      <groupId>javax.servlet</groupId>
      <artifactId>jstl</artifactId>
      <version>1.2</version>
    </dependency>
    <dependency>
      <groupId>taglibs</groupId>
      <artifactId>standard</artifactId>
      <version>1.1.2</version>
    </dependency>

在这里插入图片描述
在这里插入图片描述

SpringMVC框架提供的转发和重定向

1
重定向
自动添加项目路径名
在这里插入图片描述

ResponseBody响应json数据

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

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

        <dependency>
            <groupId>com.fasterxml.jackson.core</groupId>
            <artifactId>jackson-core</artifactId>
            <version>2.9.0</version>
        </dependency>
        
        <dependency>
            <groupId>com.fasterxml.jackson.core</groupId>
            <artifactId>jackson-annotations</artifactId>
            <version>2.9.0</version>
        </dependency>

SpringMVC实现文件上传

		<dependency>
            <groupId>commons-fileupload</groupId>
            <artifactId>commons-fileupload</artifactId>
            <version>1.3.1</version>
        </dependency>
        <dependency>
            <groupId>commons-io</groupId>
            <artifactId>commons-io</artifactId>
            <version>2.4</version>
        </dependency>

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

springmvc 跨服务器方式的文件上传

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

SpringMVC的异常处理

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

SpringMVC框架中的拦截器

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

简易例子

在这里插入图片描述
在这里插入图片描述

拦截器中的方法

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
思考:执行,且1的posthandle不会执行
在这里插入图片描述
验证用户是否登录
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

SSM整合

在这里插入图片描述

spring环境搭建

applicationContext.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:aop="http://www.springframework.org/schema/aop" 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">
        <!-- 开启注解扫描,要扫描的是service和dao层的注解,要忽略web层注解,因为web层让SpringMVC框架 去管理 -->
        <context:component-scan base-package="item">
              <!-- 配置要忽略的注解 不管Controller-->
             <context:exclude-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
        </context:component-scan>

</beans>

在这里插入图片描述

spring整合springmvc

ctrl+n可以搜索类所在包位置
在web.xml中配置DispatcherServlet前端控制器,且使用监听器加载spring配置文件
在这里插入图片描述
在项目启动的时候,就去加载applicationContext.xml的配置文件,在web.xml中配置
ContextLoaderListener监听器(该监听器只能加载WEB-INF目录下的applicationContext.xml的配置文件)。

<!DOCTYPE web-app PUBLIC
 "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
 "http://java.sun.com/dtd/web-app_2_3.dtd" >

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

<!--  配置spring的监听器-->
  <listener>
    <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
  </listener>
<!--  设置配置文件的路径-->
  <context-param>
    <param-name>contextConfigLocation</param-name>
    <param-value>classpath:applicationContext.xml</param-value>
  </context-param>


  <servlet>
    <servlet-name>dispatcherServlet</servlet-name>
    <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<!--    加载springmvc.xml-->
    <init-param>
      <param-name>contextConfigLocation</param-name>
      <param-value>classpath:springmvc.xml</param-value>
    </init-param>
<!--    启动服务器,创建该servlet-->
    <load-on-startup>1</load-on-startup>
  </servlet>
  <servlet-mapping>
    <servlet-name>dispatcherServlet</servlet-name>
    <url-pattern>/</url-pattern>
  </servlet-mapping>

<!--解决中文乱码的过滤器-->
  <filter>
    <filter-name>characterEncodingFilter</filter-name>
    <filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
    <init-param>
      <param-name>encoding</param-name>
      <param-value>UTF-8</param-value>
    </init-param>
  </filter>
  <filter-mapping>
    <filter-name>characterEncodingFilter</filter-name>
    <url-pattern>/*</url-pattern>
  </filter-mapping>
</web-app>

创建springmvc.xml的配置文件,编写配置文件

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

    <!-- 扫描controller的注解,别的不扫描 -->
    <context:component-scan base-package="item">
        <context:include-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
    </context:component-scan>

    <!--    配置的视图解析器对象-->
    <bean id="internalResourceViewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver">
        <property name="prefix" value="/WEB-INF/pages/"/>
        <property name="suffix" value=".jsp"/>
    </bean>

    <!--    过滤静态资源-->
    <mvc:resources location="/css/" mapping="/css/**"/>
    <mvc:resources location="/images/" mapping="/images/**"/>
    <mvc:resources location="/js/" mapping="/js/**"/>

    <!-- 开启对SpringMVC注解的支持 -->
    <mvc:annotation-driven />
</beans>

在这里插入图片描述
在这里插入图片描述

Spring整合MyBatis框架

Mybatis使用注解方式

8.*版本的mysql,需要在applicationcontext.xml添加

<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
        <!--        连接数据库的必备信息-->
        <property name="driverClass" value="com.mysql.cj.jdbc.Driver"/>
        <property name="jdbcUrl" value="jdbc:mysql://localhost:3306/ssm?serverTimezone=UTC"/>
        <property name="user" value="root"/>
        <property name="password" value="582211"/>
    </bean>

在这里插入图片描述
在AccountDao接口中添加@Repository注解

在service中注入dao对象,进行测试

package item.service.impl;

import item.dao.AccountDao;
import item.domain.Account;
import item.service.AccountService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.util.List;

@Service("accountService")
public class AccountServiceImpl implements AccountService {
    @Autowired
    private AccountDao dao;
    @Override
    public List<Account> findAll() {
        System.out.println("业务层:查询所有账户...");
        return dao.findAll();
    }

    @Override
    public void saveAccount(Account account) {
        dao.saveAccount(account);
    }
}

首页index
在这里插入图片描述
点击后
在这里插入图片描述
事务管理

<!--    配置spring框架声明事务管理-->
<!--    配置事务管理器-->
    <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <property name="dataSource" ref="dataSource"/>
    </bean>
<!--    配置事务通知-->
    <tx:advice id="txAdvice" transaction-manager="transactionManager">
        <tx:attributes>
            <tx:method name="find*" read-only="true"/>
            <tx:method name="*" isolation="DEFAULT"/>
        </tx:attributes>
    </tx:advice>
<!--    配置aop增强-->
    <aop:config>
        <aop:advisor advice-ref="txAdvice" pointcut="execution(* item.service.impl.*ServiceImpl.*(..))"/>
    </aop:config>

编写表单
在这里插入图片描述
Controller里的方法
在这里插入图片描述

Mybatis使用xml方式

编写AccountDao 映射配置文件
在这里插入图片描述
在这里插入图片描述
编写SqlMapConfig 配置文件
在这里插入图片描述
简易测试一下

package item.test;

import item.dao.AccountDao;
import item.domain.Account;
import item.service.AccountService;
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 org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

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

public class TestSpring {
    @Test
    public void run1(){
//        加载配置文件
        ApplicationContext context = new ClassPathXmlApplicationContext("classpath:applicationContext.xml");
        AccountService service = context.getBean("accountService", AccountService.class);
        List<Account> list = service.findAll();
        System.out.println(list);
    }
    @Test
    public void run2() throws Exception{
        InputStream is = Resources.getResourceAsStream("SqlMapConfig.xml");
        SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(is);
        SqlSession sqlSession = factory.openSession(true);
        AccountDao dao = sqlSession.getMapper(AccountDao.class);
        dao.saveAccount(new Account("小刘", 2800.0));
        sqlSession.close();
        is.close();
    }
}

在这里插入图片描述
在这里插入图片描述
此时mybatis配置文件为空
在这里插入图片描述
第一步:Spring 接管 MyBatis 的 Session 工厂
在applicacontext里如下配置
在这里插入图片描述
在这里插入图片描述
第二步:配置自动扫描所有 Mapper 接口和文件
在这里插入图片描述
第三步:配置 spring 的事务
在这里插入图片描述
测试
在这里插入图片描述
完整测试
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

  • 3
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值