Spring 框架学习(二)

大概内容

  1. 基于aspectj的注解aop操作
  2. spring的jdbcTemplate操作
  3. spring配置连接池
  4. spring事务管理

基于aspectj的注解aop操作

  1. 使用注解方式创建aop操作

第一步 :创建对象

1
2
3
<!-- 1  配置对象 --> 
<bean id="book" class="cn.shelhon.cn.shelhon.aop.Book"> </bean>
<bean id="myBook" class="cn.shelhon.cn.shelhon.aop.Book"> </bean>

第二步 :在spring核心配置文件里面开启AOP操作

1
2
<!-- 开启aop操作 --> 
<aop:aspectj-autoproxy> </aop:aspectj-autoproxy>

第三步 : 在增强类上面使用注解完成aop操作

1
2
3
@Aspect public class Mybook {
//在方法上面使用注解完成增强配置
@Before(value = "execution(* cn.shelhon.aop.Book.*(..)")

Spring的jdbcTemplate操作

实现crud操作

- 增加、修改、删除,调用模板update方法
- 查询某个值时候,调用queryForObject方法
- 自己写实现类封装数据
- 查询对象,调用queryForObject方法
- 查询list集合,调用query方法
  1. spring框架一站式框架

    • 针对javaee三层,每一层都有解决技术
    • 在dao层,使用 jdbcTemplate
  2. spring对不同的持久化层技术都进行封装

其中jdbcTemplate对jdbc进行封装

  1. jdbcTemplate使用和dbutils使用很相似,都数据库进行crud操作

增加

  1. 导入jdbcTemplate使用的jar包

  2. 创建对象,设置数据库信息

  3. 创建JdbcTemplate对象,设置数据源

  4. 调用jdbcTemplate对象里面的方法

操作的时候,记得还要导入驱动数据库的jar包

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// 添加操作 
@Test public void add()
{
//创建对象,设置数据库信息
DriverManagerDataSource dataSource = new DriverManagerDataSource();

dataSource.setDriverClassName("conm.mysql.jdbc.Driver");

dataSource.setUrl("jdbc:mysql://test");

dataSource.setUsername("root");

dataSource.setPassword("12345");

// 创建JdbcTemplate对象,设置数据源
JdbcTemplate jdbcTempalate = new JdbcTemplate(dataSource);
//调用jdbcTemplate对象里面的方法
String sql = "insert into user value(?,?)";

int rows = jdbcTempalate.update(sql,"lucy","22");

System.out.println(rows);
}

特别要注意的是,在运行的时候报错出现的问题

1
org.springframework.jdbc.CannotGetJdbcConnectionException: Could not get JDBC Connection; nested exception is java.sql.SQLException: Unknown character set index for field '255' received from server.

这个问题是由于我使用的数据库版本比较新,导致旧的mysql-connector-java的版本使用的时候报错。

(1) MYSQL 5.5 之前, UTF8 编码只支持1-3个字节;从MYSQL5.5开始,可支持4个字节UTF编码utf8mb4;如emoji表情需要使用utf8mb4

(2) 如果服务器级使用的编码是utf8mb4(在客户端链接后使用sql语句show variables like ‘char%’可查看所有编码),而mysql的jar包低版本不支持utf8mb4,连接时报错”Unknown character set index for field ‘224’ received from server.”

(3) 建议使用mysql-connector-java-5.1.30-bin.jar

(4) 注意:如果数据库不支持utf8mb4,使用mysql-connector-java-5.1.30-bin.jar的jar包时则会报错,此时应该使用低版本的jar包。

然后这个时候我又用了最新版本的mysql-connector的jar包
又报错

1
java.lang.UnsupportedClassVersionError: com/mysql/jdbc/Driver : Unsupported major.minor version 52.0

这种问题是jdk版本和mysql数据库版本不兼容
从网上寻找的答案有如下:

1、jdk7+老版5.0驱动com/mysql/jdbc/Driver

2、jdk8+新版6.0驱动com/mysql/cj/jdbc/Driver

所以就是最新的jar是不能跟jdk1.7搭配使用的。
最后解决问题的是这样的搭配

  • jdk1.7.0_80
  • mysql-connector-java 5.1.46
  • spring-jdbc 4.2.4.RELEASE

更新

更新的方法跟增加差不多。

1
2
3
4
//调用jdbcTemplate对象里面的方法 
String sql = "update user set password=? where name=?";
int rows = jdbcTemplate.update(sql,"lucy","99");
System.out.println(rows);

删除

删除也是同理

1
2
3
4
//调用jdbcTemplate对象里面的方法 
String sql = "delete from user where name=?";
int rows = jdbcTemplate.update(sql,"null");
System.out.println(rows);

查询

1、查询表中有多少记录

1
2
3
4
5
// 创建JdbcTemplate对象,设置数据源
JdbcTemplate jdbcTemplate = new JdbcTemplate(dataSource);
String sql = "select count(*) from user";
int count =jdbcTemplate.queryForObject(sql,Integer.class);
System.out.println(count); }

2、查询返回对象
jdbc底层原理

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
@Test public void testJDBC(){
Connection conn = null;
PreparedStatement psmt =null;
ResultSet rs = null;
try {
Class.forName("com.mysql.jdbc.Driver");
//创建连接
conn = DriverManager.getConnection("jdbc:mysql:///test", "root", "12345");
//编写sql语句
String sql = "select * from user where name =?";
//预编译sql语句
psmt = conn.prepareStatement(sql);
//设置参数值
psmt.setString(1, "lucy");
//遍历结果集
rs =psmt.executeQuery();
while(rs.next()){
//得到返回值
String username =rs.getString("name");
String password = rs.getString("password");
//放到User对象里面
User user =new User();
user.setUsername(username);
user.setPassword(password);
System.out.println(user);
}
}
catch (Exception e) {
e.printStackTrace();
}
finally {
try{
rs.close();
psmt.close();
conn.close();;
}catch(SQLException e){
e.printStackTrace();
}
}
}

User.java代码

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
package cn.shelhon.jdbc;   

public class User {
private String username;
private String password;

public String getUsername() {
return username;
}

public String getPassword() {
return password;
}

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

public void setPassword(String password) {
this.password = password;
}

@Override
public String toString() {
return "User{" +
"username='" + username + '\'' +
", password='" + password + '\'' +
'}';
}
}

具体的实现

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
@Test
public void testObject(){
//创建对象以及设置数据库信息
DriverManagerDataSource dataSource = new DriverManagerDataSource();
dataSource.setDriverClassName("com.mysql.jdbc.Driver");
dataSource.setUrl("jdbc:mysql:///test");
dataSource.setUsername("root");
dataSource.setPassword("12345");
// 创建JdbcTemplate对象,设置数据源
JdbcTemplate jdbcTemplate = new JdbcTemplate(dataSource); //写sql语句,根据name来查询
String sql ="select * from user where name = ?";
//调用jdbctempla的方法实现
//第二个参数是接口ROWMapper,需要自己写个类,自己做数据封装 User user=jdbcTemplate.queryForObject(sql,new MyRowMapper(),"rose");
System.out.println(user);
}
}
class MyRowMapper implements RowMapper<User>{

public User mapRow(ResultSet rs, int i) throws SQLException {
//从结果集里把数据拿到
String username = rs.getString("name");
String password = rs.getString("password");
//把得到的数据封装到对象里面
User user = new User();
user.setUsername(username);
user.setPassword(password);
return user;
}
}

3、查询返回list集合

1
2
3
4
5
6
7
8
9
10
11
12
13
14
@Test public void testList(){
//创建对象以及设置数据库信息
DriverManagerDataSource dataSource = new DriverManagerDataSource();
dataSource.setDriverClassName("com.mysql.jdbc.Driver");
dataSource.setUrl("jdbc:mysql:///test");
dataSource.setUsername("root");
dataSource.setPassword("12345");
// 创建JdbcTemplate对象,设置数据源
JdbcTemplate jdbcTemplate = new JdbcTemplate(dataSource); //写sql语句,根据name来查询
String sql ="select * from user";
//调用jdbctempla的方法实现
//第二个参数是接口ROWMapper,需要自己写个类,自己做数据封装
List<User> list =jdbcTemplate.query(sql,new MyRowMapper());
System.out.println(list); }

Spring配置连接池

配置c3p0连接池

  1. 导入jar包
    c3p0-0.9.2.1
    mchange-commons-java-0.2.3.4

  2. 创建spring文件,建立连接池

1
2
3
4
5
6
7
<!-- 配置c3p0连接池 --> 
<bean id ="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
<!-- 注入属性值-->
<property name="driverClass" value="com.mysql.jdbc.Driver"> </property>
<property name="jdbcUrl" value="jdbc:mysql://test"> </property>
<property name="user" value="root"> </property>
<property name="password" value="12345"> </property> </bean>

dao使用jdbcTemplate

  1. 创建service和dao,配置service和dao对象,在service注入dao对象
1
2
3
4
<bean id="userService" class="cn.shelhon.c3p0.UserService"> 
<property name="userDao" ref="userDao"></property>
</bean>
<bean id="userDao" class="cn.shelhon.c3p0.UserDao"></bean>
  1. 创建jdbcTemplate对象,把模板对象注入到dao里面
    创建对象
    1
    2
    3
    4
    5
    private JdbcTemplate jdbcTemplate; 
    //添加操作
    public void setJdbcTemplate(JdbcTemplate jdbcTemplate) {
    this.jdbcTemplate = jdbcTemplate;
    }

注入dao里面

1
2
3
<bean id="userDao" class="cn.shelhon.c3p0.UserDao">
<property name="jdbcTemplate" ref="jdbcTemplate"> </property>
</bean>

  1. 在jdbcTemplate对象里面注入dataSource
1
2
3
4
<!-- 创建JdbcTemplate对象 --> 
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<!-- 把datasource传递到模板对象里面 -->
<property name="dataSource" ref="dataSource"> </property> </bean>

关于数据库语句

因为我的表里用了主键,在插入新的数据的时候

1
2
String sql ="insert into user values(?,?)"; 
jdbcTemplate.update(sql,"李峰","asd");

按照这语法就报错了

后来改这样就通过了

1
2
String sql="insert into user(id,name,password) values(null,?,?)"; 
jdbcTemplate.update(sql,"李峰","asd");


Spring事务管理

事务概念

  1. 什么是事务
    Spring事务 的本质其实就是数据库对事务的支持,没有数据库的事务支持,spring是无法提供事务功能的。

理解事务之前,先讲一个你日常生活中最常干的事:取钱。
比如你去ATM机取1000块钱,大体有两个步骤:首先输入密码金额,银行卡扣掉1000元钱;然后ATM出1000元钱。这两个步骤必须是要么都执行要么都不执行。如果银行卡扣除了1000块但是ATM出钱失败的话,你将会损失1000元;如果银行卡扣钱失败但是ATM却出了1000块,那么银行将损失1000元。所以,如果一个步骤成功另一个步骤失败对双方都不是好事,如果不管哪一个步骤失败了以后,整个取钱过程都能回滚,也就是完全取消所有操作的话,这对双方都是极好的。
事务就是用来解决类似问题的。事务是一系列的动作,它们综合在一起才是一个完整的工作单元,这些动作必须全部完成,如果有一个失败的话,那么事务就会回滚到最开始的状态,仿佛什么都没发生过一样。
在企业级应用程序开发中,事务管理必不可少的技术,用来确保数据的完整性和一致性。

  1. 事务特性:四大特性(简称ACID)

    • 原子性(Atomicity):
      原子性是指事务包含的所有操作要么全部成功,要么全部失败回滚。
      因此事务的操作如果成功就必须要完全应用到数据库,如果操作失败则不能对数据库有任何影响。
    • 一致性(Consistency):
      一致性是指事务必须使数据库从一个一致性状态变换到另一个一致性状态,也就是说一个事务执行之前和执行之后都必须处于一致性状态。

      拿转账来说,假设用户A和用户B两者的钱加起来一共是5000,那么不管A和B之间如何转账,转几次账,事务结束后两个用户的钱相加起来应该还得是5000,这就是事务的一致性。

    • 隔离性(Isolation):
      隔离性是当多个用户并发访问数据库时,比如操作同一张表时,数据库为每一个用户开启的事务,不能被其他事务的操作所干扰,多个并发事务之间要相互隔离。
      即要达到这么一种效果:对于任意两个并发的事务T1和T2,在事务T1看来,T2要么在T1开始之前就已经结束,要么在T1结束之后才开始,这样每个事务都感觉不到有其他事务在并发地执行。
    • 持久性(Durability):
      持久性是指一个事务一旦被提交了,那么对数据库中的数据的改变就是永久性的,即便是在数据库系统遇到故障的情况下也不会丢失提交事务的操作。
  2. 不考虑隔离性产生读问题

    • 脏读:
      脏读是指在一个事务处理过程里读取了另一个未提交的事务中的数据。
    • 不可重复读:
      不可重复读是指在对于数据库中的某个数据,一个事务范围内多次查询却返回了不同的数据值,这是由于在查询间隔,被另一个事务修改并提交了。
      不可重复读和脏读的区别是,脏读是某一事务读取了另一个事务未提交的脏数据,而不可重复读则是读取了前一事务提交的数据。
    • 虚读(幻读):
       幻读是事务非独立执行时发生的一种现象。例如事务T1对一个表中所有的行的某个数据项做了从“1”修改为“2”的操作,这时事务T2又对这个表中插入了一行数据项,而这个数据项的数值还是为“1”并且提交给数据库。而操作事务T1的用户如果再查看刚刚修改的数据,会发现还有一行没有修改,其实这行是从事务T2中添加的,就好像产生幻觉一样,这就是发生了幻读。

  幻读和不可重复读都是读取了另一条已经提交的事务(这点就脏读不同),所不同的是不可重复读查询的都是同一个数据项,而幻读针对的是一批数据整体(比如数据的个数)。

  1. 解决读问题
    设置隔离级别
    数据库隔离级别:
    ① Serializable (串行化):可避免脏读、不可重复读、幻读的发生。
    ② Repeatable read (可重复读):可避免脏读、不可重复读的发生。
    ③ Read committed (读已提交):可避免脏读的发生。
    ④ Read uncommitted (读未提交):最低级别,任何情况都无法保证。

spring进行事务管理api

spring事务管理有两种方法

  1. 编程式事务管理(一般不用)
  2. 声明式事务管理:
    • 基于xml配置文件实现
    • 基于注解实现

spring事务管理的api介绍

spring事务管理高层抽象主要包括3个接口

  • PlatformTransactionManager
    事务管理器
  • TransactionDefinition
    事务定义信息(隔离、传播、超时、只读)
  • TransactionStatus
    事务具体运行状态

    其中spring针对不同的dao层框架,提供不同的实现类
    比如:

    • org.springframework.jdbc.datasource.DataSourceTransactionManager是使用springJDBC或者iBatis进行持久化数据时使用。
    • org.springframework.orm.hibernate5.HibernateTransactionManager使用Hiber5版本进行持久化数据时使用

搭建转账环境

首先配置事务管理器

  1. 创建数据库的表,添加数据

  2. 创建service类和dao类,完成注入关系。

1
2
3
4
5
6
7
8
9
10
11
12
13
<bean id="ordersService" class="cn.shelhon.service.OrdersService">
<property name="ordersDao" ref="ordersDao">
</property>
</bean>

<bean id="ordersDao" class="cn.shelhon.Dao.OrdersDao">
<property name="jdbcTemplate" ref="jdbcTemplate"> </property>
</bean>

<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<property name="dataSource" ref="dataSource">
</property>
</bean>
  • service层又叫业务逻辑层
  • dao层,单独对数据库操作层,在dao层不添加业务
  1. 比较齐全的约束
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<?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">

</beans>
  1. 需求
    甲 转账给 乙 1000
    甲 少 1000
    乙 多 1000

先在dao层写操作数据库的方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class OrdersDao {
//注入jdbcTemplate模板
private JdbcTemplate jdbcTemplate;
public void setJdbcTemplate(JdbcTemplate jdbcTemplate) {
this.jdbcTemplate = jdbcTemplate;
}
//提供对数据库操作的方法,不写业务操作 // 少钱的方法 public void lessMoney(){
String sql ="update account set salary=salary-? where name = ?";
jdbcTemplate.update(sql,1000,"甲");
}
// 加钱的方法
public void addMoney(){
String sql ="update account set salary=salary+? where name =?";
jdbcTemplate.update(sql,1000,"乙");
}
}

然后再service层调用dao层写业务逻辑

1
2
3
4
5
6
7
8
9
10
11
12
public class OrdersService {
private OrdersDao ordersDao; public void setOrdersDao(OrdersDao ordersDao) {
this.ordersDao = ordersDao;
}
//调用dao的方法
//业务逻辑层,写转账业务 public void accountMoney(){
// 甲少1000
ordersDao.lessMoney();;
// 乙多1000
ordersDao.addMoney();
}
}
  1. 产生的问题 :
    • 如果甲扣钱了以后,出现异常,乙的钱并没有增加,钱丢失了
  1. 解决:
    -添加事务解决,出现异常进行回滚操作

spring进行事务配置(声明式)

基于xml配置文件

  1. 配置文件使用aop思想
1
2
3
4
5
<!-- 第一步配置事务管理器 -->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<!-- 注入datasource -->
<property name="dataSource" ref="dataSource"> </property>
</bean>
  1. 配置事务的增强
1
2
3
4
5
6
7
<!-- 第二步 配置事务增强 --> 
<tx:advice id ="txadvice" transaction-manager="transactionManager">
<!-- 做事务操作 -->
<tx:attributes>
<!-- 设置进行事务操作的方法匹配规则 -->
<tx:method name="account*"/>
</tx:attributes> </tx:advice>
  1. 配置切面
1
2
3
4
5
6
7
<!-- 第三步 --> 
<!-- 配置切面 -->
<aop:config>
<!-- 切入点 -->
<aop:pointcut expression ="execution(* cn.shelhon.service.OrdersService.*(..))" id = "pointcut1"/>
<!--切面-->
<aop:advisor advice-ref="txadvice" pointcut-ref="pointcut1"/> </aop:config>

经过上面的配置以后,出现异常就能自动回滚操作

基于注解方式

  1. 第一步也是要先配置事务管理器
1
2
3
4
5
<!-- 配置事务管理器 --> 
<bean id="dataSourceTransactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource">
</property>
</bean>
  1. 配置事务注解

    1
    2
    <!-- 开启事务注解 --> 
    <tx:annotation-driven transaction-manager="dataSourceTransactionManager"/>
  2. 要在使用事务的方法所在类上添加注解

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
@Transactional public class OrdersService {
private OrdersDao ordersDao;
public void setOrdersDao(OrdersDao ordersDao) {
this.ordersDao = ordersDao;
}
//调用dao的方法
//业务逻辑层,写转账业务
public void accountMoney(){
// 甲少1000
ordersDao.lessMoney();;
//出现异常
// int i =10/0;
// 乙多1000
ordersDao.addMoney();
}
}
Just for fun!
------------- 文章已经到尾 -------------