IDEA常用的快捷键

Alt+回车 导入包,自动修正

Ctrl+N 查找类

Ctrl+Shift+N 查找文件

Ctrl+Alt+L 格式化代码

Ctrl+Alt+O 优化导入的类和包

Alt+Insert 生成代码(如get,set方法,构造函数等)

Ctrl+E或者Alt+Shift+C 最近更改的代码

Ctrl+R 替换文本

Ctrl+F 查找文本

Ctrl+Shift+Space 自动补全代码

Ctrl+空格 代码提示

Ctrl+Alt+Space 类名或接口名提示

Ctrl+P 方法参数提示

Ctrl+Shift+Alt+N 查找类中的方法或变量

Alt+Shift+C 对比最近修改的代码

Shift+F6 重构-重命名

Ctrl+Shift+先上键

Ctrl+X 删除行

Ctrl+D 复制行

Ctrl+/ 或 Ctrl+Shift+/ 注释(// 或者// )

Ctrl+J 自动代码

Ctrl+E 最近打开的文件

Ctrl+H 显示类结构图

Ctrl+Q 显示注释文档

Alt+F1 查找代码所在位置

Alt+1 快速打开或隐藏工程面板

Ctrl+Alt+ left/right 返回至上次浏览的位置

Alt+ left/right 切换代码视图

Alt+ Up/Down 在方法间快速移动定位

Ctrl+Shift+Up/Down 代码向上/下移动。

F2 或Shift+F2 高亮错误或警告快速定位

代码标签输入完成后,按Tab,生成代码。

选中文本,按Ctrl+Shift+F7 ,高亮显示所有该文本,按Esc高亮消失。

Ctrl+W 选中代码,连续按会有其他效果

选中文本,按Alt+F3 ,逐个往下查找相同文本,并高亮显示。

Ctrl+Up/Down 光标跳转到第一行或最后一行下

Ctrl+B 快速打开光标处的类或方法

Intellij IDEA最常用快捷键

1.Ctrl+E,可以显示最近编辑的文件列表

2.Shift+Click可以关闭文件

3.Ctrl+[或]可以跳到大括号的开头结尾

4.Ctrl+Shift+Backspace可以跳转到上次编辑的地方

5.Ctrl+F12,可以显示当前文件的结构

6.Ctrl+F7可以查询当前元素在当前文件中的引用,然后按F3可以选择

7.Ctrl+N,可以快速打开类

8.Ctrl+Shift+N,可以快速打开文件

9.Alt+Q可以看到当前方法的声明

10.Ctrl+W可以选择单词继而语句继而行继而函数

11.Alt+F1可以将正在编辑的元素在各个面板中定位

12.Ctrl+P,可以显示参数信息

13.Ctrl+Shift+Insert可以选择剪贴板内容并插入

14.Alt+Insert可以生成构造器/Getter/Setter等

15.Ctrl+Alt+V 可以引入变量。例如把括号内的SQL赋成一个变量

16.Ctrl+Alt+T可以把代码包在一块内,例如try/catch

17.Alt+Up and Alt+Down可在方法间快速移动

Java-JDBC

JDBC:

概念:Java DataBase Connectivity Java 数据库连接, Java语言操作数据库

* JDBC本质:其实是官方(sun公司)定义的一套操作所有关系型数据库的规则,即接口。各个数据库厂商去实现这套接口,提供数据库驱动jar包。我们可以使用这套接口(JDBC)编程,真正执行的代码是驱动jar包中的实现类。

快速入门:

* 步骤:
    1. 导入驱动jar包 mysql-connector-java-5.1.37-bin.jar
        1.复制mysql-connector-java-5.1.37-bin.jar到项目的libs目录下
        2.右键-->Add As Library
    2. 注册驱动
    3. 获取数据库连接对象 Connection
    4. 定义sql
    5. 获取执行sql语句的对象 Statement
    6. 执行sql,接受返回结果
    7. 处理结果
    8. 释放资源

* 代码实现:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
//1. 导入驱动jar包
//2.注册驱动
Class.forName("com.mysql.jdbc.Driver");
//3.获取数据库连接对象
Connection conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/db3", "root", "root");
//4.定义sql语句
String sql = "update account set balance = 500 where id = 1";
//5.获取执行sql的对象 Statement
Statement stmt = conn.createStatement();
//6.执行sql
int count = stmt.executeUpdate(sql);
//7.处理结果
System.out.println(count);
//8.释放资源
stmt.close();
conn.close();

详解各个对象:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
1. DriverManager:驱动管理对象
* 功能:
1. 注册驱动:告诉程序该使用哪一个数据库驱动jar
static void registerDriver(Driver driver) :注册与给定的驱动程序 DriverManager 。
写代码使用: Class.forName("com.mysql.jdbc.Driver");
通过查看源码发现:在com.mysql.jdbc.Driver类中存在静态代码块
static {
try {
java.sql.DriverManager.registerDriver(new Driver());
} catch (SQLException E) {
throw new RuntimeException("Can't register driver!");
}
}

注意:mysql5之后的驱动jar包可以省略注册驱动的步骤。
2. 获取数据库连接:
* 方法:static Connection getConnection(String url, String user, String password)
* 参数:
* url:指定连接的路径
* 语法:jdbc:mysql://ip地址(域名):端口号/数据库名称
* 例子:jdbc:mysql://localhost:3306/db3
* 细节:如果连接的是本机mysql服务器,并且mysql服务默认端口是3306,则url可以简写为:jdbc:mysql:///数据库名称
* user:用户名
* password:密码

2. Connection:数据库连接对象
1. 功能:
1. 获取执行sql 的对象
* Statement createStatement()
* PreparedStatement prepareStatement(String sql)
2. 管理事务:
* 开启事务:setAutoCommit(boolean autoCommit) :调用该方法设置参数为false,即开启事务
* 提交事务:commit()
* 回滚事务:rollback()
3. Statement:执行sql的对象
1. 执行sql
1. boolean execute(String sql) :可以执行任意的sql 了解
2. int executeUpdate(String sql) :执行DML(insert、update、delete)语句、DDL(create,alter、drop)语句
* 返回值:影响的行数,可以通过这个影响的行数判断DML语句是否执行成功 返回值>0的则执行成功,反之,则失败。
3. ResultSet executeQuery(String sql) :执行DQL(select)语句
2. 练习:
1. account表 添加一条记录
2. account表 修改记录
3. account表 删除一条记录

代码:
Statement stmt = null;
Connection conn = null;
try {
//1. 注册驱动
Class.forName("com.mysql.jdbc.Driver");
//2. 定义sql
String sql = "insert into account values(null,'王五',3000)";
//3.获取Connection对象
conn = DriverManager.getConnection("jdbc:mysql:///db3", "root", "root");
//4.获取执行sql的对象 Statement
stmt = conn.createStatement();
//5.执行sql
int count = stmt.executeUpdate(sql);//影响的行数
//6.处理结果
System.out.println(count);
if(count > 0){
System.out.println("添加成功!");
}else{
System.out.println("添加失败!");
}

} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (SQLException e) {
e.printStackTrace();
}finally {
//stmt.close();
//7. 释放资源
//避免空指针异常
if(stmt != null){
try {
stmt.close();
} catch (SQLException e) {
e.printStackTrace();
}
}

if(conn != null){
try {
conn.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}

4. ResultSet:结果集对象,封装查询结果
* boolean next(): 游标向下移动一行,判断当前行是否是最后一行末尾(是否有数据),如果是,则返回false,如果不是则返回true
* getXxx(参数):获取数据
* Xxx:代表数据类型 如: int getInt() , String getString()
* 参数:
1. int:代表列的编号,从1开始 如: getString(1)
2. String:代表列名称。 如: getDouble("balance")

* 注意:
* 使用步骤:
1. 游标向下移动一行
2. 判断是否有数据
3. 获取数据

//循环判断游标是否是最后一行末尾。
while(rs.next()){
//获取数据
//6.2 获取数据
int id = rs.getInt(1);
String name = rs.getString("name");
double balance = rs.getDouble(3);

System.out.println(id + "---" + name + "---" + balance);
}

* 练习:
* 定义一个方法,查询emp表的数据将其封装为对象,然后装载集合,返回。
1. 定义Emp类
2. 定义方法 public List<Emp> findAll(){}
3. 实现方法 select * from emp;

5. PreparedStatement:执行sql的对象
1. SQL注入问题:在拼接sql时,有一些sql的特殊关键字参与字符串的拼接。会造成安全性问题
1. 输入用户随便,输入密码:a' or 'a' = 'a
2. sql:select * from user where username = 'fhdsjkf' and password = 'a' or 'a' = 'a'

2. 解决sql注入问题:使用PreparedStatement对象来解决
3. 预编译的SQL:参数使用?作为占位符
4. 步骤:
1. 导入驱动jar包 mysql-connector-java-5.1.37-bin.jar
2. 注册驱动
3. 获取数据库连接对象 Connection
4. 定义sql
* 注意:sql的参数使用?作为占位符。 如:select * from user where username = ? and password = ?;
5. 获取执行sql语句的对象 PreparedStatement Connection.prepareStatement(String sql)
6. 给?赋值:
* 方法: setXxx(参数1,参数2)
* 参数1:?的位置编号 从1 开始
* 参数2:?的值
7. 执行sql,接受返回结果,不需要传递sql语句
8. 处理结果
9. 释放资源

5. 注意:后期都会使用PreparedStatement来完成增删改查的所有操作
1. 可以防止SQL注入
2. 效率更高

抽取JDBC工具类 : JDBCUtils

* 目的:简化书写
* 分析:
    1. 注册驱动也抽取
    2. 抽取一个方法获取连接对象
        * 需求:不想传递参数(麻烦),还得保证工具类的通用性。
        * 解决:配置文件
            jdbc.properties
                url=
                user=
                password=


    3. 抽取一个方法释放资源

* 代码实现:
    public class JDBCUtils {
    private static String url;
    private static String user;
    private static String password;
    private static String driver;
    /**
     * 文件的读取,只需要读取一次即可拿到这些值。使用静态代码块
     */
    static{
        //读取资源文件,获取值。

        try {
            //1. 创建Properties集合类。
            Properties pro = new Properties();

            //获取src路径下的文件的方式--->ClassLoader 类加载器
            ClassLoader classLoader = JDBCUtils.class.getClassLoader();
            URL res  = classLoader.getResource("jdbc.properties");
            String path = res.getPath();
            System.out.println(path);///D:/IdeaProjects/itcast/out/production/day04_jdbc/jdbc.properties
            //2. 加载文件
           // pro.load(new FileReader("D:\\IdeaProjects\\itcast\\day04_jdbc\\src\\jdbc.properties"));
            pro.load(new FileReader(path));

            //3. 获取数据,赋值
            url = pro.getProperty("url");
            user = pro.getProperty("user");
            password = pro.getProperty("password");
            driver = pro.getProperty("driver");
            //4. 注册驱动
            Class.forName(driver);
        } catch (IOException e) {
            e.printStackTrace();
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
    }

    /**
     * 获取连接
     * @return 连接对象
     */
public static Connection getConnection() throws SQLException {
    return DriverManager.getConnection(url, user, password);
}

/**
 * 释放资源
 * @param stmt
 * @param conn
 */
public static void close(Statement stmt,Connection conn){
    if( stmt != null){
        try {
            stmt.close();
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }

    if( conn != null){
        try {
            conn.close();
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }
}



​ /**
​ * 释放资源
​ * @param stmt
​ * @param conn
​ */
​ public static void close(ResultSet rs,Statement stmt, Connection conn){
​ if( rs != null){
​ try {
​ rs.close();
​ } catch (SQLException e) {
​ e.printStackTrace();
​ }
​ }

​ if( stmt != null){
​ try {
​ stmt.close();
​ } catch (SQLException e) {
​ e.printStackTrace();
​ }
​ }

if( conn != null){
try {
conn.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}

}

* 练习:
    * 需求:
        1. 通过键盘录入用户名和密码
        2. 判断用户是否登录成功
            * select * from user where username = "" and password = "";
            * 如果这个sql有查询结果,则成功,反之,则失败

    * 步骤:
        1. 创建数据库表 user
            CREATE TABLE USER(
                id INT PRIMARY KEY AUTO_INCREMENT,
                username VARCHAR(32),
                PASSWORD VARCHAR(32)

            );

            INSERT INTO USER VALUES(NULL,'zhangsan','123');
            INSERT INTO USER VALUES(NULL,'lisi','234');

        2. 代码实现:
            public class JDBCDemo9 {

                public static void main(String[] args) {
                    //1.键盘录入,接受用户名和密码
                    Scanner sc = new Scanner(System.in);
                    System.out.println("请输入用户名:");
                    String username = sc.nextLine();
                    System.out.println("请输入密码:");
                    String password = sc.nextLine();
                    //2.调用方法
                    boolean flag = new JDBCDemo9().login(username, password);
                    //3.判断结果,输出不同语句
                    if(flag){
                        //登录成功
                        System.out.println("登录成功!");
                    }else{
                        System.out.println("用户名或密码错误!");
                    }



                    ​                
​                    }


​                    /**
​                     * 登录方法
​                     */
​                    public boolean login(String username ,String password){
​                        if(username == null || password == null){
​                            return false;
​                        }
​                        //连接数据库判断是否登录成功
​                        Connection conn = null;
​                        Statement stmt =  null;
​                        ResultSet rs = null;
​                        //1.获取连接
​                        try {
​                            conn =  JDBCUtils.getConnection();
​                            //2.定义sql
​                            String sql = "select * from user where username = '"+username+"' and password = '"+password+"' ";
​                            //3.获取执行sql的对象
​                            stmt = conn.createStatement();
​                            //4.执行查询
​                            rs = stmt.executeQuery(sql);
​                            //5.判断
​                           /* if(rs.next()){//如果有下一行,则返回true
​                                return true;
​                            }else{
​                                return false;
​                            }*/
​                           return rs.next();//如果有下一行,则返回true
​                
                    } catch (SQLException e) {
                        e.printStackTrace();
                    }finally {
                        JDBCUtils.close(rs,stmt,conn);
                    }
                    return false;
                    }
                }


JDBC控制事务

1. 事务:一个包含多个步骤的业务操作。如果这个业务操作被事务管理,则这多个步骤要么同时成功,要么同时失败。
2. 操作:
    1. 开启事务
    2. 提交事务
    3. 回滚事务
3. 使用Connection对象来管理事务
    * 开启事务:setAutoCommit(boolean autoCommit) :调用该方法设置参数为false,即开启事务
        * 在执行sql之前开启事务
    * 提交事务:commit() 
        * 当所有sql都执行完提交事务
    * 回滚事务:rollback() 
        * 在catch中回滚事务

4. 代码:
    public class JDBCDemo10 {

        public static void main(String[] args) {
            Connection conn = null;
            PreparedStatement pstmt1 = null;
            PreparedStatement pstmt2 = null;

            try {
                //1.获取连接
                conn = JDBCUtils.getConnection();
                //开启事务
                conn.setAutoCommit(false);

                //2.定义sql
                //2.1 张三 - 500
                String sql1 = "update account set balance = balance - ? where id = ?";
                //2.2 李四 + 500
                String sql2 = "update account set balance = balance + ? where id = ?";
                //3.获取执行sql对象
                pstmt1 = conn.prepareStatement(sql1);
                pstmt2 = conn.prepareStatement(sql2);
                //4. 设置参数
                pstmt1.setDouble(1,500);
                pstmt1.setInt(2,1);

                pstmt2.setDouble(1,500);
                pstmt2.setInt(2,2);
                //5.执行sql
                pstmt1.executeUpdate();
                // 手动制造异常
                int i = 3/0;

                pstmt2.executeUpdate();
                //提交事务
                conn.commit();
            } catch (Exception e) {
                //事务回滚
                try {
                    if(conn != null) {
                        conn.rollback();
                    }
                } catch (SQLException e1) {
                    e1.printStackTrace();
                }
                e.printStackTrace();
            }finally {
                JDBCUtils.close(pstmt1,conn);
                JDBCUtils.close(pstmt2,null);
            }
       }
}

数据库连接池

1. 概念:其实就是一个容器(集合),存放数据库连接的容器。
        当系统初始化好后,容器被创建,容器中会申请一些连接对象,当用户来访问数据库时,从容器中获取连接对象,用户访问完之后,会将连接对象归还给容器。

2. 好处:
    1. 节约资源
    2. 用户访问高效

3. 实现:
    1. 标准接口:DataSource   javax.sql包下的
        1. 方法:
            * 获取连接:getConnection()
            * 归还连接:Connection.close()。如果连接对象Connection是从连接池中获取的,那么调用Connection.close()方法,则不会再关闭连接了。而是归还连接

    2. 一般我们不去实现它,有数据库厂商来实现
        1. C3P0:数据库连接池技术
        2. Druid:数据库连接池实现技术,由阿里巴巴提供的

4. C3P0:数据库连接池技术

    * 步骤:
        1. 导入jar包 (两个) c3p0-0.9.5.2.jar mchange-commons-java-0.2.12.jar ,
            * 不要忘记导入数据库驱动jar包
        2. 定义配置文件:
            * 名称: c3p0.properties 或者 c3p0-config.xml
            * 路径:直接将文件放在src目录下即可。

        3. 创建核心对象 数据库连接池对象 ComboPooledDataSource
        4. 获取连接: getConnection
    * 代码:
         //1.创建数据库连接池对象
        DataSource ds  = new ComboPooledDataSource();
        //2. 获取连接对象
        Connection conn = ds.getConnection();
5. Druid:数据库连接池实现技术,由阿里巴巴提供的
    1. 步骤:
        1. 导入jar包 druid-1.0.9.jar
        2. 定义配置文件:
            * 是properties形式的
            * 可以叫任意名称,可以放在任意目录下
        3. 加载配置文件。Properties
        4. 获取数据库连接池对象:通过工厂来来获取  DruidDataSourceFactory
        5. 获取连接:getConnection
    * 代码:
         //3.加载配置文件
        Properties pro = new Properties();
        InputStream is = DruidDemo.class.getClassLoader().getResourceAsStream("druid.properties");
        pro.load(is);
        //4.获取连接池对象
        DataSource ds = DruidDataSourceFactory.createDataSource(pro);
        //5.获取连接
        Connection conn = ds.getConnection();
    2. 定义工具类
        1. 定义一个类 JDBCUtils
        2. 提供静态代码块加载配置文件,初始化连接池对象
        3. 提供方法
            1. 获取连接方法:通过数据库连接池获取连接
            2. 释放资源
            3. 获取连接池的方法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
* 代码:

public class JDBCUtils {

//1.定义成员变量 DataSource
private static DataSource ds ;

static{
try {
//1.加载配置文件
Properties pro = new Properties();
pro.load(JDBCUtils.class.getClassLoader().getResourceAsStream("druid.properties"));
//2.获取DataSource
ds = DruidDataSourceFactory.createDataSource(pro);
} catch (IOException e) {
e.printStackTrace();
} catch (Exception e) {
e.printStackTrace();
}
}

/**
* 获取连接
*/
public static Connection getConnection() throws SQLException {
return ds.getConnection();
}

/**
* 释放资源
*/
public static void close(Statement stmt,Connection conn){
/* if(stmt != null){
try {
stmt.close();
} catch (SQLException e) {
e.printStackTrace();
}
}

if(conn != null){
try {
conn.close();//归还连接
} catch (SQLException e) {
e.printStackTrace();
}
}*/

close(null,stmt,conn);
}


public static void close(ResultSet rs , Statement stmt, Connection conn){


if(rs != null){
try {
rs.close();
} catch (SQLException e) {
e.printStackTrace();
}
}


if(stmt != null){
try {
stmt.close();
} catch (SQLException e) {
e.printStackTrace();
}
}

if(conn != null){
try {
conn.close();//归还连接
} catch (SQLException e) {
e.printStackTrace();
}
}
}

/**
* 获取连接池方法
*/

public static DataSource getDataSource(){
return ds;
}

}

Spring JDBC

* Spring框架对JDBC的简单封装。提供了一个JDBCTemplate对象简化JDBC的开发
* 步骤:
    1. 导入jar包
    2. 创建JdbcTemplate对象。依赖于数据源DataSource
        * JdbcTemplate template = new JdbcTemplate(ds);

    3. 调用JdbcTemplate的方法来完成CRUD的操作
        * update():执行DML语句。增、删、改语句
        * queryForMap():查询结果将结果集封装为map集合,将列名作为key,将值作为value 将这条记录封装为一个map集合
            * 注意:这个方法查询的结果集长度只能是1
        * queryForList():查询结果将结果集封装为list集合
            * 注意:将每一条记录封装为一个Map集合,再将Map集合装载到List集合中
        * query():查询结果,将结果封装为JavaBean对象
            * query的参数:RowMapper
                * 一般我们使用BeanPropertyRowMapper实现类。可以完成数据到JavaBean的自动封装
                * new BeanPropertyRowMapper<类型>(类型.class)
        * queryForObject:查询结果,将结果封装为对象
            * 一般用于聚合函数的查询

    4. 练习:
        * 需求:
            1. 修改1号数据的 salary 为 10000
            2. 添加一条记录
            3. 删除刚才添加的记录
            4. 查询id为1的记录,将其封装为Map集合
            5. 查询所有记录,将其封装为List
            6. 查询所有记录,将其封装为Emp对象的List集合
            7. 查询总记录数

        * 代码:

            import cn.itcast.domain.Emp;
            import cn.itcast.utils.JDBCUtils;
            import org.junit.Test;
            import org.springframework.jdbc.core.BeanPropertyRowMapper;
            import org.springframework.jdbc.core.JdbcTemplate;
            import org.springframework.jdbc.core.RowMapper;

            import java.sql.Date;
            import java.sql.ResultSet;
            import java.sql.SQLException;
            import java.util.List;
            import java.util.Map;

            public class JdbcTemplateDemo2 {

                //Junit单元测试,可以让方法独立执行


​ //1. 获取JDBCTemplate对象
​ private JdbcTemplate template = new JdbcTemplate(JDBCUtils.getDataSource());
​ /**
​ * 1. 修改1号数据的 salary 为 10000
​ */
​ @Test
​ public void test1(){

//2. 定义sql
String sql = “update emp set salary = 10000 where id = 1001”;
//3. 执行sql
int count = template.update(sql);
System.out.println(count);
}

/**
 * 2. 添加一条记录
 */
@Test
public void test2(){
    String sql = "insert into emp(id,ename,dept_id) values(?,?,?)";
    int count = template.update(sql, 1015, "郭靖", 10);
    System.out.println(count);

}

/**
 * 3.删除刚才添加的记录
 */
@Test
public void test3(){
    String sql = "delete from emp where id = ?";
    int count = template.update(sql, 1015);
    System.out.println(count);
}

/**
 * 4.查询id为1001的记录,将其封装为Map集合
 * 注意:这个方法查询的结果集长度只能是1
 */
@Test
public void test4(){
    String sql = "select * from emp where id = ? or id = ?";
    Map<String, Object> map = template.queryForMap(sql, 1001,1002);
    System.out.println(map);
    //{id=1001, ename=孙悟空, job_id=4, mgr=1004, joindate=2000-12-17, salary=10000.00, bonus=null, dept_id=20}

}

/**
 * 5. 查询所有记录,将其封装为List
 */
@Test
public void test5(){
    String sql = "select * from emp";
    List<Map<String, Object>> list = template.queryForList(sql);

    for (Map<String, Object> stringObjectMap : list) {
        System.out.println(stringObjectMap);
    }
}

/**
 * 6. 查询所有记录,将其封装为Emp对象的List集合
 */

@Test
public void test6(){
    String sql = "select * from emp";
    List<Emp> list = template.query(sql, new RowMapper<Emp>() {

        @Override
        public Emp mapRow(ResultSet rs, int i) throws SQLException {
            Emp emp = new Emp();
            int id = rs.getInt("id");
            String ename = rs.getString("ename");
            int job_id = rs.getInt("job_id");
            int mgr = rs.getInt("mgr");
            Date joindate = rs.getDate("joindate");
            double salary = rs.getDouble("salary");
            double bonus = rs.getDouble("bonus");
            int dept_id = rs.getInt("dept_id");

            emp.setId(id);
            emp.setEname(ename);
            emp.setJob_id(job_id);
            emp.setMgr(mgr);
            emp.setJoindate(joindate);
            emp.setSalary(salary);
            emp.setBonus(bonus);
            emp.setDept_id(dept_id);

            return emp;
        }
    });


​ for (Emp emp : list) {
​ System.out.println(emp);
​ }
​ }

/**
* 6. 查询所有记录,将其封装为Emp对象的List集合
*/

    @Test
    public void test6_2(){
        String sql = "select * from emp";
        List<Emp> list = template.query(sql, new BeanPropertyRowMapper<Emp>(Emp.class));
        for (Emp emp : list) {
            System.out.println(emp);
        }
    }

    /**
     * 7. 查询总记录数
     */

    @Tes
    public void test7(){
        String sql = "select count(id) from emp";
        Long total = template.queryForObject(sql, Long.class);
        System.out.println(total);
    }

}

Spring学习笔记04-jdbcTemplate和事物控制

Spring中的 JdbcTemplate

待写~

以前的笔记

Spring中的事务控制

Spring事务控制我们要明确的

image-20200205221657283

## 中事务控制的 API介绍

PlatformTransactionManager

此接口是 spring 的事务管理器,它里面提供了我们常用的操作事务的方法,如下图:

image-20200205221757940

我们在开发中都是使用它的实现类,如下图:

image-20200205221824445

TransactionDefinition

它是事务的定义信息对象,里面有如下方法:

image-20200205221913375

事务的隔离级别

image-20200205221942346

事务的传播行为

image-20200205222006210

是否是只读事务

建议查询时设置为只读。

TransactionStatus

image-20200205222100018

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

环境搭建

第一步:拷贝必要的 jar 包到工程的 lib 目录

image-20200205222151326

第二步:创建 spring 的配置文件并导入约束

image-20200205222207743

第三步:准备数据库表和实体类

image-20200205222226052
image-20200205222238246

第四步:编写业务层接口和实现类

image-20200205222303999
image-20200205222317541

第五步:编写 Dao 接口和实现类

image-20200205222335145
image-20200205222400040
image-20200205222424417
image-20200205222445999

第六步:在配置文件中配置业务层和持久层对

image-20200205222513235

配置步骤

第一步:配置事务管理器

image-20200205222543228

image-20200205222554974

第二步:配置事务的通知引用事务管理器

image-20200205222612778

第三步:配置事务的属性

image-20200205222628931

第四步:配置 AOP 切入点表达式

image-20200205222712478

第五步:配置切入点表达式和事务通知的对应关系

image-20200205222730653

基于注解的配置方式  

第一步:配置事务管理器并注入数据源

image-20200205222835357

第二步:在业务层使用@Transactional 注解

image-20200205222923812
image-20200205222934473

第三步:在配置文件中开启 spring 对注解事务的支持

1
2
<!-- 开启 spring 对注解事务的支持 --> 
<tx:annotation-driven transaction-manager="transactionManager"/>

不使用 xml的配置方式

1
2
3
4
@Configuration 
@EnableTransactionManagement
public class SpringTxConfiguration {
//里面配置数据源,配置 JdbcTemplate,配置事务管理器。在之前的步骤已经写过了。 }

解决idea Error:java 不支持发行版本5

在Intellij idea中新建了一个Maven项目,运行时报错如下:Error : java 不支持发行版本5

img

检查一下项目及环境使用的Java编译版本配置。

《1》在Intellij中点击“File” -->“Project Structure”,看一下“Project”和“Module”栏目中Java版本是否与本地一致:

img

img

如果不一致,改成本地使用的Java版本。

《2》点击“Settings”-->“Bulid, Execution,Deployment”-->“Java Compiler”,Target bytecode version设为本地Java版本。(可以在Default Settings中把Project bytecode version 一劳永逸地配置成本地Java版本)

img

Default Settings:

img

以上两步都配置好之后,重新运行应该就不会报上述错误了。

Java-动态代理

动态代理的特点

字节码随用随创建,随用随加载。
它与静态代理的区别也在于此。因为静态代理是字节码一上来就创建好,并完成加载。
装饰者模式就是静态代理的一种体现。

动态代理常用的有两种方式

基于接口的动态代理    
  提供者:JDK 官方的 Proxy 类。
  要求:被代理类最少实现一个接口。
基于子类的动态代理
  提供者:第三方的 CGLib,如果报 asmxxxx 异常,需要导入 asm.jar。
  要求:被代理类不能用 final 修饰的类(最终类)。

基于接口的动态代理

此处我们使用的是一个演员的例子:
在很久以前,演员和剧组都是直接见面联系的。没有中间人环节。
而随着时间的推移,产生了一个新兴职业:经纪人(中间人),这个时候剧组再想找演员就需要通过经纪
人来找了。下面我们就用代码演示出来。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
/**
* 一个经纪公司的要求:
* 能做基本的表演和危险的表演
*/
public interface IActor {
/**
* 基本演出
* @param money
*/
public void basicAct(float money);
/**
* 危险演出
* @param money
*/
public void dangerAct(float money);
}

/**
* 一个演员
*/

---------------------------------------------

//实现了接口,就表示具有接口中的方法实现。即:符合经纪公司的要求
public class Actor implements IActor{

public void basicAct(float money){
System.out.println("拿到钱,开始基本的表演:"+money);
}

public void dangerAct(float money){
System.out.println("拿到钱,开始危险的表演:"+money);
}
}

----------------------------------------------
public class Client {

public static void main(String[] args) {
//一个剧组找演员:
final Actor actor = new Actor();//直接

/**
* 代理:
* 间接。
* 获取代理对象:
* 要求:
* 被代理类最少实现一个接口
* 创建的方式
* Proxy.newProxyInstance(三个参数)
* 参数含义:
* ClassLoader:和被代理对象使用相同的类加载器。.
* Interfaces:和被代理对象具有相同的行为。实现相同的接口。.
* InvocationHandler:如何代理。
* 策略模式:使用场景是:
* 数据有了,目的明确。
* 如何达成目标,就是策略。
*
*/
IActor proxyActor = (IActor) Proxy.newProxyInstance(
actor.getClass().getClassLoader(),
actor.getClass().getInterfaces(),
new InvocationHandler() {
/**
* 执行被代理对象的任何方法,都会经过该方法。
* 此方法有拦截的功能。
*
* 参数:
* proxy:代理对象的引用。不一定每次都用得到
* method:当前执行的方法对象
* args:执行方法所需的参数
* 返回值:
* 当前执行方法的返回值
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable {
String name = method.getName();
Float money = (Float) args[0];
Object rtValue = null;
//每个经纪公司对不同演出收费不一样,此处开始判断
if("basicAct".equals(name)){
//基本演出,没有 2000 不演
if(money > 2000){
//看上去剧组是给了 8000,实际到演员手里只有 4000
//这就是我们没有修改原来 basicAct 方法源码,对方法进行了增强
rtValue = method.invoke(actor, money/2);
}
}
if("dangerAct".equals(name)){
//危险演出,没有 5000 不演
if(money > 5000){
//看上去剧组是给了 50000,实际到演员手里只有 25000
//这就是我们没有修改原来 dangerAct 方法源码,对方法进行了增强
rtValue = method.invoke(actor, money/2);
}
}
return rtValue;
}
});
//没有经纪公司的时候,直接找演员。
// actor.basicAct(1000f);
// actor.dangerAct(5000f);

//剧组无法直接联系演员,而是由经纪公司找的演员
proxyActor.basicAct(8000f);
proxyActor.dangerAct(50000f);
}
}

使用 CGLib 的 Enhancer 类创建代理对象

还是那个演员的例子,只不过不让他实现接口。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71

/**
* 一个演员
*/
public class Actor{//没有实现任何接口

public void basicAct(float money){
System.out.println("拿到钱,开始基本的表演:"+money);
}

public void dangerAct(float money){
System.out.println("拿到钱,开始危险的表演:"+money);
}
}


-------------------------------------
public class Client {
/**
* 基于子类的动态代理
* 要求:
* 被代理对象不能是最终类
* 用到的类:
* Enhancer
* 用到的方法:
* create(Class, Callback)
* 方法的参数:
* Class:被代理对象的字节码
* Callback:如何代理
* @param args
*/
public static void main(String[] args) {
final Actor actor = new Actor();
Actor cglibActor = (Actor) Enhancer.create(actor.getClass(),
new MethodInterceptor() {
/**
* 执行被代理对象的任何方法,都会经过该方法。在此方法内部就可以对被代理对象的任何
方法进行增强。
*
* 参数:
* 前三个和基于接口的动态代理是一样的。
* MethodProxy:当前执行方法的代理对象。
* 返回值:
* 当前执行方法的返回值
*/
@Override
public Object intercept(Object proxy, Method method, Object[] args,
MethodProxy methodProxy) throws Throwable {
String name = method.getName();
Float money = (Float) args[0];
Object rtValue = null;
if("basicAct".equals(name)){
//基本演出
if(money > 2000){
rtValue = method.invoke(actor, money/2);
}
}
if("dangerAct".equals(name)){
//危险演出
if(money > 5000){
rtValue = method.invoke(actor, money/2);
}
}
return rtValue;
}
});
cglibActor.basicAct(10000);
cglibActor.dangerAct(100000);
}
}

思考:
这个故事(示例)讲完之后,我们从中受到什么启发呢?它到底能应用在哪呢?

Spring学习笔记03-AOP

AOP 概念

AOP:全称是 Aspect Oriented Programming 即:面向切面编程。简单的说它就是把我们程序重复的代码抽取出来,在需要执行的时候,使用动态代理的技术,在不修改源码的基础上,对我们的已有方法进行增强。

image-20200204211913152

AOP 的作用及优势

作用:
     在程序运行期间,不修改源码对已有方法进行增强。
优势:
     减少重复代码
     提高开发效率
     维护方便

AOP 的实现方式

使用动态代理技术   动态代理:

AOP 的具体应用

案例中问题

下面是客户的业务层实现类。我们能看出什么问题吗?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
客户的业务层实现类
/**
* 账户的业务层实现类
*/
public class AccountServiceImpl implements IAccountService {

private IAccountDao accountDao;

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

@Override
public void saveAccount(Account account) throws SQLException {
accountDao.save(account);
}

@Override
public void updateAccount(Account account) throws SQLException{
accountDao.update(account);
}

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

@Override
public Account findAccountById(Integer accountId) throws SQLException {
return accountDao.findById(accountId);
}

@Override
public List<Account> findAllAccount() throws SQLException{
return accountDao.findAll();
}
}


问题就是:
事务被自动控制了。换言之,我们使用了 connection 对象的 setAutoCommit(true)
此方式控制事务,如果我们每次都执行一条 sql 语句,没有问题,但是如果业务方法一次要执行多条 sql语句,这种方式就无法实现功能了。

问题的解决

解决办法:
     让业务层来控制事务的提交和回滚。(这个我们之前已经在 web 阶段讲过了)

改造后的业务层实现类:
     注:此处没有使用 spring 的 IoC.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
/**
* 账户的业务层实现类
*/
public class AccountServiceImpl implements IAccountService {
private IAccountDao accountDao = new AccountDaoImpl();


@Override
public void saveAccount(Account account) {
try {
TransactionManager.beginTransaction();
accountDao.save(account);
TransactionManager.commit();
} catch (Exception e) {
TransactionManager.rollback();
e.printStackTrace();
}finally {
TransactionManager.release();
}
}

@Override
public void updateAccount(Account account) {
try {
TransactionManager.beginTransaction();
accountDao.update(account);
TransactionManager.commit();
} catch (Exception e) {
TransactionManager.rollback();
e.printStackTrace();
}finally {
TransactionManager.release();
}
}

@Override
public void deleteAccount(Integer accountId) {
try {
TransactionManager.beginTransaction();
accountDao.delete(accountId);
TransactionManager.commit();
} catch (Exception e) {
TransactionManager.rollback();
e.printStackTrace();
}finally {
TransactionManager.release();
}
}

@Override
public Account findAccountById(Integer accountId) {
Account account = null;
try {
TransactionManager.beginTransaction();
account = accountDao.findById(accountId);
TransactionManager.commit();
return account;
} catch (Exception e) {
TransactionManager.rollback();
e.printStackTrace();
}finally {
TransactionManager.release();
}
return null;
}

@Override
public List<Account> findAllAccount() {
List<Account> accounts = null;
try {
TransactionManager.beginTransaction();
accounts = accountDao.findAll();
TransactionManager.commit();
return accounts;
} catch (Exception e) {
TransactionManager.rollback();
e.printStackTrace();
}finally {
TransactionManager.release();
}
return null;
}

@Override
public void transfer(String sourceName, String targetName, Float money) {
try {
TransactionManager.beginTransaction();
Account source = accountDao.findByName(sourceName);
Account target = accountDao.findByName(targetName);
source.setMoney(source.getMoney()-money);
target.setMoney(target.getMoney()+money);
accountDao.update(source);
int i=1/0;
accountDao.update(target);
TransactionManager.commit();
} catch (Exception e) {
TransactionManager.rollback();
e.printStackTrace();
}finally {
TransactionManager.release();
}
}
}

TransactionManager 类的代码:
/**
* 事务控制类
*/
public class TransactionManager {

//定义一个 DBAssit
private static DBAssit dbAssit = new DBAssit(C3P0Utils.getDataSource(),true);


//开启事务
public static void beginTransaction() {
try {
dbAssit.getCurrentConnection().setAutoCommit(false);
} catch (SQLException e) {
e.printStackTrace();
}
}

//提交事务
public static void commit() {
try {
dbAssit.getCurrentConnection().commit();
} catch (SQLException e) {
e.printStackTrace();
}
}

//回滚事务

public static void rollback() {
try {
dbAssit.getCurrentConnection().rollback();
} catch (SQLException e) {
e.printStackTrace();
}
}

//释放资源
public static void release() {
try {
dbAssit.releaseConnection();
} catch (Exception e) {
e.printStackTrace();
}
}
}

新的问题

上一小节的代码,通过对业务层改造,已经可以实现事务控制了,但是由于我们添加了事务控制,也产生了一个新的问题:

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

思考:
     这个问题能不能解决呢?
     答案是肯定的,使用下一小节中提到的技术。

动态代理

解决案例中的问题

1
等吧这个代码写了再码上来~

Spring 中 AOP 的细节

说明

我们学习 spring 的 aop,就是通过配置的方式,实现上一章节的功能。

AOP 相关术语

Joinpoint(连接点):
        所谓连接点是指那些被拦截到的点。在 spring 中,这些点指的是方法,因为 spring 只支持方法类型的连接点。
1
2
Pointcut(切入点):
所谓切入点是指我们要对哪些 Joinpoint 进行拦截的定义。
1
2
3
Advice(通知/增强):
所谓通知是指拦截到 Joinpoint 之后所要做的事情就是通知。
通知的类型:前置通知,后置通知,异常通知,最终通知,环绕通知。
1
2
Introduction(引介):
引介是一种特殊的通知在不修改类代码的前提下, Introduction 可以在运行期为类动态地添加一些方法或 Field。
1
2
3
4
5
6
7
8
9
Target(目标对象):
代理的目标对象。
Weaving(织入):
是指把增强应用到目标对象来创建新的代理对象的过程。
spring 采用动态代理织入,而 AspectJ 采用编译期织入和类装载期织入。
Proxy(代理):
一个类被 AOP 织入增强后,就产生一个结果代理类。
Aspect(切面):
是切入点和通知(引介)的结合。

学习 spring 中的 AOP 要明确的事

a、开发阶段(我们做的)
     编写核心业务代码(开发主线):大部分程序员来做,要求熟悉业务需求。
     把公用代码抽取出来,制作成通知。(开发阶段最后再做):AOP 编程人员来做。
     在配置文件中,声明切入点与通知间的关系,即切面。:AOP 编程人员来做。
b、运行阶段(Spring 框架完成的)
     Spring 框架监控切入点方法的执行。一旦监控到切入点方法被运行,使用代理机制,动态创建目标对象的代理对象,根据通知类别,在代理对象的对应位置,将通知对应的功能织入,完成完整的代码逻辑运行。

## 关于代理的选择

在 spring 中,框架会根据目标类是否实现了接口来决定采用哪种动态代理的方式。

基于 XML 的 AOP 配置

示例:
我们在学习 spring 的 aop 时,采用账户转账作为示例。
并且把 spring 的 ioc 也一起应用进来。

环境搭建

待写代码~

第一步:准备必要的代码

此处包含了实体类,业务层和持久层代码。我们沿用上一章节中的代码即可。

第二步:拷贝必备的 jar 包到工程的 lib 目录

image-20200204220400522

第三步:创建 spring 的配置文件并导入约束

image-20200204220631930

第四步:配置 spring 的 ioc

image-20200204220700310
image-20200204220720630

第五步:抽取公共代码制作成通知

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
/**
* 事务控制类
*/
public class TransactionManager {

//定义一个 DBAssit
private DBAssit dbAssit ;

public void setDbAssit(DBAssit dbAssit) {
this.dbAssit = dbAssit;
}

//开启事务
public void beginTransaction() {
try {
dbAssit.getCurrentConnection().setAutoCommit(false);
} catch (SQLException e) {
e.printStackTrace();
}
}

//提交事务
public void commit() {
try {
dbAssit.getCurrentConnection().commit();
} catch (SQLException e) {
e.printStackTrace();
}
}

//回滚事务
public void rollback() {
try {
dbAssit.getCurrentConnection().rollback();
} catch (SQLException e) {
e.printStackTrace();
}
}

//释放资源
public void release() {
try {
dbAssit.releaseConnection();
} catch (Exception e) {
e.printStackTrace();
}
}
}

配置步骤

第一步:把通知类用 bean 标签配置起来

1
2
3
4
<!-- 配置通知 -->
<bean id="txManager" class="com.itheima.utils.TransactionManager">
<property name="dbAssit" ref="dbAssit"></property>
</bean>

第二步:使用 aop:config 声明 aop 配置

aop:config:
     作用:用于声明开始 aop 的配置
<aop:config>

     <!-- 配置的代码都写在此处 -->
</aop:config>

第三步:使用 aop:aspect 配置切面

aop:aspect:
     作用:
         用于配置切面。
     属性:
         id:给切面提供一个唯一标识。
         ref:引用配置好的通知类 bean 的 id。
<aop:aspect id="txAdvice" ref="txManager">
         <!--配置通知的类型要写在此处-->
</aop:aspect>

第四步:使用 aop:pointcut 配置切入点表达式

aop:pointcut:
     作用:
         用于配置切入点表达式。就是指定对哪些类的哪些方法进行增强。
     属性:
         expression:用于定义切入点表达式。
         id:用于给切入点表达式提供一个唯一标识
1
2
3
4
<aop:pointcut expression="execution(
public void com.itheima.service.impl.AccountServiceImpl.transfer(
java.lang.String, java.lang.String, java.lang.Float)
)" id="pt1"/>

第五步:使用 aop:xxx 配置对应的通知类型

aop:before
作用:
用于配置前置通知。指定增强的方法在切入点方法之前执行
属性:
method:用于指定通知类中的增强方法名称
ponitcut-ref:用于指定切入点的表达式的引用
poinitcut:用于指定切入点表达式
执行时间点:
切入点方法执行之前执行

1
<aop:before method="beginTransaction" pointcut-ref="pt1"/>

aop:after-returning

作用:
    用于配置后置通知
属性:
    method:指定通知中方法的名称。
    pointct:定义切入点表达式
    pointcut-ref:指定切入点表达式的引用
执行时间点:
    切入点方法正常执行之后。它和异常通知只能有一个执行
1
<aop:after-returning method="commit" pointcut-ref="pt1"/>

aop:after-throwing
作用:
用于配置异常通知
属性:
method:指定通知中方法的名称。
pointct:定义切入点表达式
pointcut-ref:指定切入点表达式的引用
执行时间点:
切入点方法执行产生异常后执行。它和后置通知只能执行一个

1
<aop:after-throwing method="rollback" pointcut-ref="pt1"/>

aop:after
作用:
用于配置最终通知
属性:
method:指定通知中方法的名称。
pointct:定义切入点表达式
pointcut-ref:指定切入点表达式的引用
执行时间点:
无论切入点方法执行时是否有异常,它都会在其后面执行。

1
<aop:after method="release" pointcut-ref="pt1"/>

## 切入点表达式说明

image-20200204221658313
image-20200204221708460

环绕通知

image-20200204221741749
image-20200204221802374

基于注解的 AOP 配置

环境搭建

第一步:准备必要的代码和 jar包

拷贝上一小节的工程即可

第二步:在配置文件中导入 context 的名称空间

image-20200204222018293

第三步:把资源使用注解配置

image-20200204222036847
image-20200204222052181

第四步:在配置文件中指定 spring 要扫描的包

image-20200204222121354

配置步骤

第一步:把通知类也使用注解配置

1
2
3
4
5
6
7
8
9
/**
* 事务控制类
*/
@Component("txManager")
public class TransactionManager {
//定义一个 DBAssit
@Autowired
private DBAssit dbAssit ;
}

第二步:在通知类上使用@Aspect 注解声明为切面

作用:
把当前类声明为切面类。

1
2
3
4
5
6
7
8
9
10
11
/**
* 事务控制类
*/
@Component("txManager")
@Aspect//表明当前类是一个切面类
public class TransactionManager {

//定义一个 DBAssit
@Autowired
private DBAssit dbAssit ;
}

第三步:在增强的方法上使用注解配置通知

@Before
作用:
把当前方法看成是前置通知。
属性:
value:用于指定切入点表达式,还可以指定切入点表达式的引用。

1
2
3
4
5
6
7
8
9
//开启事务
@Before("execution(* com.itheima.service.impl.*.*(..))")
public void beginTransaction() {
try {
dbAssit.getCurrentConnection().setAutoCommit(false);
} catch (SQLException e) {
e.printStackTrace();
}
}

@AfterReturning
作用:
把当前方法看成是后置通知。
属性:
value:用于指定切入点表达式,还可以指定切入点表达式的引用

1
2
3
4
5
6
7
8
9
10
//提交事务
@AfterReturning("execution(* com.itheima.service.impl.*.*(..))")
public void commit() {

try {
dbAssit.getCurrentConnection().commit();
} catch (SQLException e) {
e.printStackTrace();
}
}

@AfterThrowing
作用:
把当前方法看成是异常通知。
属性:
value:用于指定切入点表达式,还可以指定切入点表达式的引用```

1
2
3
4
5
6
7
8
9
//回滚事务
@AfterThrowing("execution(* com.itheima.service.impl.*.*(..))")
public void rollback() {
try {
dbAssit.getCurrentConnection().rollback();
} catch (SQLException e) {
e.printStackTrace();
}
}

@After
作用:
把当前方法看成是最终通知。
属性:
value:用于指定切入点表达式,还可以指定切入点表达式的引用

1
2
3
4
5
6
7
8
9
//释放资源
@After("execution(* com.itheima.service.impl.*.*(..))")
public void release() {
try {
dbAssit.releaseConnection();
} catch (Exception e) {
e.printStackTrace();
}
}

第四步:在 spring 配置文件中开启 spring 对注解 AOP 的支持

1
2
<!-- 开启 spring 对注解 AOP 的支持 -->
<aop:aspectj-autoproxy/>

环绕通知注解配置

@Around
作用:
把当前方法看成是环绕通知。
属性:
value:用于指定切入点表达式,还可以指定切入点表达式的引用。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
/**
* 环绕通知
* @param pjp
* @return
*/
@Around("execution(* com.itheima.service.impl.*.*(..))")
public Object transactionAround(ProceedingJoinPoint pjp) {
//定义返回值
Object rtValue = null;
try {
//获取方法执行所需的参数
Object[] args = pjp.getArgs();
//前置通知:开启事务
beginTransaction();
//执行方法
rtValue = pjp.proceed(args);
//后置通知:提交事务
commit();
}catch(Throwable e) {
//异常通知:回滚事务
rollback();
e.printStackTrace();
}finally {
//最终通知:释放资源
release();
}
return rtValue;
}

切入点表达式注解

@Pointcut
作用:
指定切入点表达式
属性:
value:指定表达式的内容

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
@Pointcut("execution(* com.itheima.service.impl.*.*(..))")
private void pt1() {}

引用方式:
/**
* 环绕通知
* @param pjp
* @return
*/
@Around("pt1()")//注意:千万别忘了写括号
public Object transactionAround(ProceedingJoinPoint pjp) {
//定义返回值
Object rtValue = null;
try {
//获取方法执行所需的参数
Object[] args = pjp.getArgs();
//前置通知:开启事务
beginTransaction();
//执行方法
rtValue = pjp.proceed(args);
//后置通知:提交事务
commit();
}catch(Throwable e) {
//异常通知:回滚事务
rollback();
e.printStackTrace();
}finally {
//最终通知:释放资源
release();
}
return rtValue;
}

不使用 XML 的配置方式

1
2
3
4
5
@Configuration
@ComponentScan(basePackages="com.itheima")
@EnableAspectJAutoProxy
public class SpringConfiguration {
}

Spring学习笔记02-注解

基于注解的 IOC 配置

学习基于注解的 IoC 配置,大家脑海里首先得有一个认知,即注解配置和 xml 配置要实现的功能都是一样
的,都是要降低程序间的耦合。只是配置的形式不一样。
关于实际的开发中到底使用xml还是注解,每家公司有着不同的使用习惯。所以这两种配置方式我们都需要掌握。

结合实际案例

注意:
基于注解整合时,导入约束时需要多导入一个 context 名称空间下的约束。
由于我们使用了注解配置,此时不能在继承 JdbcDaoSupport,需要自己配置一个 JdbcTemplate
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
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/context
http://www.springframework.org/schema/context/spring-context.xsd">

<!-- 告知 spring 创建容器时要扫描的包 -->
<context:component-scan base-package="com.itheima"></context:component-scan>



<!-- 配置 dbAssit -->
<bean id="dbAssit" class="com.itheima.dbassit.DBAssit">
<property name="dataSource" ref="dataSource"></property>
</bean>
<!-- 配置数据源 -->
<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
<property name="driverClass" value="com.mysql.jdbc.Driver"></property>
<property name="jdbcUrl" value="jdbc:mysql:///spring_day02"></property>
<property name="user" value="root"></property>
<property name="password" value="1234"></property>
</bean>
</beans>

常用注解

用于创建对象的

相当于:

1
<bean id="" class=""><bean/>

@Component

作用:
把资源让 spring 来管理。相当于在 xml 中配置一个 bean。
属性:
value:指定 bean 的 id。如果不指定 value 属性,默认 bean 的 id 是当前类的类名。首字母小写。

@Controller @Service @Repository

他们三个注解都是针对一个的衍生注解,他们的作用及属性都是一模一样的。
他们只不过是提供了更加明确的语义化。
@Controller:一般用于表现层的注解。
@Service:一般用于业务层的注解。
@Repository:一般用于持久层的注解。
细节:如果注解中有且只有一个属性要赋值时,且名称是 value,value 在赋值是可以不写。

用于注入数据的

相当于:

name
1
<property name="" value="">

### @Autowired

作用:
自动按照类型注入。当使用注解注入属性时,set 方法可以省略。它只能注入其他 bean 类型。当有多个
类型匹配时,使用要注入的对象变量名称作为 bean 的 id,在 spring 容器查找,找到了也可以注入成功。找不到就报错。

@Value

作用:
注入基本数据类型和 String 类型数据的
属性:
value:用于指定值

用于改变作用范围的:

**相当于:

1
<bean id="" class="" scope="">

@Scope

作用:
指定 bean 的作用范围。
属性:
value:指定范围的值。
取值:singleton prototype request session globalsession

和生命周期相关的:(了解)

相当于:

1
<bean id="" class="" init-method="" destroy-method="" />

### @PostConstruct

作用:
用于指定初始化方法。

@PreDestroy

作用:
用于指定销毁方法。

关于 Spring 注解和 XML 的选择问题

image-20200202205908326

spring 管理对象细节

基于注解的 spring IoC 配置中,bean 对象的特点和基于 XML 配置是一模一样的

spring 的纯注解配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
之所以我们现在离不开 xml 配置文件,是因为我们有一句很关键的配置:
<!-- 告知spring框架在,读取配置文件,创建容器时,扫描注解,依据注解创建对象,并存入容器中 -->
<context:component-scan base-package="com.itheima"></context:component-scan>
如果他要也能用注解配置,那么我们就离脱离 xml 文件又进了一步。
另外,数据源和 JdbcTemplate 的配置也需要靠注解来实现。
<!-- 配置 dbAssit -->
<bean id="dbAssit" class="com.itheima.dbassit.DBAssit">
<property name="dataSource" ref="dataSource"></property>
</bean>
<!-- 配置数据源 -->
<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
<property name="driverClass" value="com.mysql.jdbc.Driver"></property>
<property name="jdbcUrl" value="jdbc:mysql:///spring_day02"></property>
<property name="user" value="root"></property>
<property name="password" value="1234"></property>
</bean>

新注解说明

@Configuration

作用:
用于指定当前类是一个 spring 配置类,当创建容器时会从该类上加载注解。获取容器时需要使用
AnnotationApplicationContext(有@Configuration 注解的类.class)。
属性:
value:用于指定配置类的字节码

1
2
3
4
5
6
@Configuration
public class SpringConfiguration {
}
//注意:
//我们已经把配置文件用类来代替了,但是如何配置创建容器时要扫描的包呢?
//请看下一个注解

@ComponentScan

作用:
用于指定 spring 在初始化容器时要扫描的包。作用和在 spring 的 xml 配置文件中的:
<context:component-scan base-package=”com.itheima”/>是一样的

属性:
basePackages:用于指定要扫描的包。和该注解中的 value 属性作用一样

1
2
3
4
5
6
7
@Configuration
@ComponentScan("com.itheima")
public class SpringConfiguration {
}
//注意:
//我们已经配置好了要扫描的包,但是数据源和 JdbcTemplate 对象如何从配置文件中移除呢?
//请看下一个注解

@Bean

作用:
该注解只能写在方法上,表明使用此方法创建一个对象,并且放入 spring 容器。
属性:
name:给当前@Bean 注解方法创建的对象指定一个名称(即 bean 的 id)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
public class JdbcConfig {
/**
* 创建一个数据源,并存入 spring 容器中
* @return
*/
@Bean(name="dataSource")
public DataSource createDataSource() {
try {
ComboPooledDataSource ds = new ComboPooledDataSource();
ds.setUser("root");
ds.setPassword("1234");
ds.setDriverClass("com.mysql.jdbc.Driver");
ds.setJdbcUrl("jdbc:mysql:///spring_day02");
return ds;
} catch (Exception e) {
throw new RuntimeException(e);
}
}

/**
* 创建一个 DBAssit,并且也存入 spring 容器中
* @param dataSource
* @return
*/
@Bean(name="dbAssit")
public DBAssit createDBAssit(DataSource dataSource) {
return new DBAssit(dataSource);
}
}
//注意:
//我们已经把数据源和 DBAssit 从配置文件中移除了,此时可以删除 bean.xml 了。
//但是由于没有了配置文件,创建数据源的配置又都写死在类中了。如何把它们配置出来呢?
//请看下一个注解。

@PropertySource

作用:

​ 用于加载.properties 文件中的配置。例如我们配置数据源时,可以把连接数据库的信息写到
​ properties 配置文件中,就可以使用此注解指定 properties 配置文件的位置。
属性:
​ value[]:用于指定 properties 文件位置。如果是在类路径下,需要写上 classpath:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
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;
/**
* 创建一个数据源,并存入 spring 容器中
* @return
*/
@Bean(name="dataSource")
public DataSource createDataSource() {
try {
ComboPooledDataSource ds = new ComboPooledDataSource();
ds.setDriverClass(driver);
ds.setJdbcUrl(url);
ds.setUser(username);
ds.setPassword(password);
return ds;
}
catch (Exception e) {
throw new RuntimeException(e);
}
}
}



jdbc.properties 文件:
jdbc.driver=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/day44_ee247_spring
jdbc.username=root
jdbc.password=1234
//注意:
//此时我们已经有了两个配置类,但是他们还没有关系。如何建立他们的关系呢?
//请看下一个注解。

@Import

作用:
用于导入其他配置类,在引入其他配置类时,可以不用再写@Configuration 注解。当然,写上也没问
题。
属性:
value[]:用于指定其他配置类的字节码。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@Configuration
@ComponentScan(basePackages = "com.itheima.spring")
@Import({JdbcConfig.class})
public class SpringConfiguration {

}

@Configuration
@PropertySource("classpath:jdbc.properties")
public class JdbcConfig{

}
注意:
我们已经把要配置的都配置好了,但是新的问题产生了,由于没有配置文件了,如何获取容器呢?
请看下一小节。

通过注解获取容器

1
2
ApplicationContext ac = 
new *AnnotationConfigApplicationContext(SpringConfiguration.class);//也可以写多个class

工程结构图

image-20200202211722792

Spring 整合 Junit[掌握]

在测试类中,每个测试方法都有以下两行代码:
ApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml");
IAccountService as = ac.getBean("accountService",IAccountService.class);
这两行代码的作用是获取容器,如果不写的话,直接会提示空指针异常。所以又不能轻易删掉。

针对上述问题,我们需要的是程序能自动帮我们创建容器。一旦程序能自动为我们创建 spring 容器,我们就
无须手动创建了,问题也就解决了。
我们都知道,junit 单元测试的原理(不会的百度),但显然,junit 是无法实现的,因为它自
己都无法知晓我们是否使用了 spring 框架,更不用说帮我们创建 spring 容器了。不过好在,junit 给我们暴露
了一个注解,可以让我们替换掉它的运行器。
这时,我们需要依靠 spring 框架,因为它提供了一个运行器,可以读取配置文件(或注解)来创建容器。我
们只需要告诉它配置文件在哪就行了

## 配置步骤

第一步:导包

此处需要注意的是,导入 jar 包时,需要导入一个 spring 中 aop 的 jar 包

image-20200202212001126

第二步:使用@RunWith 注解替换原有运行器

1
2
3
4
@RunWith(SpringJUnit4ClassRunner.class)
public class AccountServiceTest {

}

第三步:使用@ContextConfiguration 指定 spring 配置文件的位置

1
2
3
4
5
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations= {"classpath:bean.xml"})
public class AccountServiceTest {

}

@ContextConfiguration 注解:

locations 属性:用于指定配置文件的位置。如果是类路径下,需要用 classpath:表明
classes 属性:用于指定注解的类。当不使用 xml 配置时,需要用此属性指定注解类的位置。

第四步:使用@Autowired 给测试类中的变量注入数据

1
2
3
4
5
6
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations= {"classpath:bean.xml"})
public class AccountServiceTest {
@Autowired
private IAccountService as ;
}