Keep Calm and Carry On

一、BOS项目概述

BOS(Business Operating System),业务操作系统,这个项目的是一个企业资源计划平台(ERP)。

1. 开发环境

开发环境 产品环境
操作系统 Windows Linux/centos
开发工具 eclipse、IDEA
数据库 MySQL MySQL
Web容器 Tomcat7.0 Tomcat7.0
浏览器 FireFox(firebug)/GOOGLE Chrome/QQBrowser

2. 技术选型

编号 工具 版本 说明
1 Struts 2 2.3.15 表现层MVC框架
2 Hibernate 3.6.10 数据层持久化框架
3 Spring 3.2.0 业务管理Ioc和AOP框架
4 Junit 4 单元测试
5 jQuery 1.8.3 JS框架
6 jQuery Easy UI 1.3.2 JS前端UI框架
7 Ztree 3.5 JS树型菜单插件
8 POI 3.9 Office文档读写组件
9 Hessian 4.0.33 RMI远程调用
10 Apache Shiro 1.2.2 权限管理框架
11 Activiti 5.13 工作流框架

二、搭建开发环境

1. 创建数据库环境

  1. 使用root用户登录MySQL数据库,创建一个数据库;
1
create database bos;
  1. 为本项目创建一个数据库用户
1
create user zhuobo identified by ‘asdf’;
  1. 为新创建的用户授权访问bos数据库
1
grant all on bos.* to zhuobo;
  1. 使用新的数据库用户登录MySQL

2. 搭建web项目环境

1. 创建动态web项目

项目的目录结构

2. 导入Struts、Spring、Hibernate、数据库驱动等jar包

3. 在web.xml中配置struts、spring

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
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
version="4.0">
<!--Struts过滤器-->
<filter>
<filter-name>struts</filter-name>
<filter-class>org.apache.struts2.dispatcher.ng.filter.StrutsPrepareAndExecuteFilter</filter-class>
</filter>

<filter-mapping>
<filter-name>struts</filter-name>
<url-pattern>/*</url-pattern>
<!--请求和转发都会被Struts拦截,默认情况下只有请求会被拦截-->
<dispatcher>REQUEST</dispatcher>
<dispatcher>FORWARD</dispatcher>
</filter-mapping>

<!--spring配置文件-->
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:applicationContext.xml</param-value>
</context-param>

<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>

</web-app>

4. 创建struts.xml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE struts PUBLIC
"-//Apache Software Foundation//DTD Struts Configuration 2.3//EN"
"http://struts.apache.org/dtds/struts-2.3.dtd">

<struts>
<!--debug mode-->
<constant name="struts.devMode" value="true" />

<package name="p1" extends="struts-default" namespace="/">
<action name="page_*_*">
<result name="success">/WEB-INF/pages/{1}/{2}.jsp</result>
</action>
</package>
</struts>

5. 创建jdbc.properties

1
2
3
4
driverClass=com.mysql.jdbc.Driver
jdbcUrl=jdbc:mysql://localhost:3306/bos
user=zhuobo
password=asdf

6. 创建applicationContext.xml

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
<?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:tx="http://www.springframework.org/schema/tx"
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
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx.xsd">

<!--hibernate-->
<context:property-placeholder location="classpath:jdbc.properties"/>

<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
<property name="driverClass" value="${driverClass}"/>
<property name="jdbcUrl" value="${jdbcUrl}"/>
<property name="user" value="${user}"/>
<property name="password" value="${password}"/>
</bean>

<bean id="sessionFactory" class="org.springframework.orm.hibernate3.LocalSessionFactoryBean">
<property name="dataSource" ref="dataSource"/>
<!--hibernate其他配置-->
<property name="hibernateProperties">
<props>
<prop key="hibernate.show_sql">true</prop>
<prop key="hibernate.format_sql">true</prop>
<prop key="hibernate.connection.autocommit">true</prop>
<prop key="hibernate.hbm2ddl.auto">update</prop>
<prop key="hibernate.dialect">org.hibernate.dialect.MySQL5Dialect</prop>
</props>
</property>
<!--映射文件-->
<property name="mappingDirectoryLocations">
<list>
<!--<value>classpath:com/zhuobo/bos/domain</value>-->
</list>
</property>
</bean>

<!--事务管理器-->
<bean id="txManager" class="org.springframework.orm.hibernate3.HibernateTransactionManager">
<property name="sessionFactory" ref="sessionFactory"/>
</bean>

<!--组件扫描,注解扫描范围-->
<context:component-scan base-package="com.zhuobo.bos"/>

<!--引用注解解析器-->
<context:annotation-config/>

<!--事务注解-->
<tx:annotation-driven/>
</beans>

7. 中文乱码问题

web.xml中设置guolvqi,使用utf-8编码

web.xml

1
2
3
4
5
6
7
8
9
10
11
12
    <!--字符编码过滤器,用于解决Tomcat7的POST请求的中文乱码问题-->
<filter>
<filter-name>characterEncodingFilter</filter-name>
<!--Spring web包提供的characterEncodingFilter只能解决POST中文乱码问题-->
<!-- <filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>-->
<filter-class>com.zhuobo.bos.web.filter.MyEncodingFilter</filter-class>
</filter>

<filter-mapping>
<filter-name>characterEncodingFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>

MyCharacterEncoding.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
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
public class MyEncodingFilter implements Filter{

@Override
public void init(FilterConfig filterConfig) throws ServletException {}
@Override
public void destroy() {}

@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
//1.设置POST请求中文乱码的问题
request.setCharacterEncoding("UTF-8");
//System.out.println("拦截请求:" + request);

//2.解决get请求的中文乱码问题
//request : RequestFacade;
HttpServletRequest hsr = (HttpServletRequest)request;
if(hsr.getMethod().equalsIgnoreCase("get")){
MyRequest myRequest = new MyRequest(hsr);
//放行请求
chain.doFilter(myRequest, response);
}else{
chain.doFilter(request, response);
}
}
}
/**
* Wrapper包装类,装饰设计模式,内部有个真实对象的引用
*/
class MyRequest extends HttpServletRequestWrapper{

private HttpServletRequest request;
private boolean isEncoding = false;//是否已经utf-8编码
public MyRequest(HttpServletRequest request) {
super(request);
this.request = request;
}

@Override
public String getParameter(String name) {
return getParameterMap().get(name)[0];
}

@Override
public Map<String, String[]> getParameterMap() {
Map<String, String[]> map = request.getParameterMap();

if(isEncoding == true){
return map;
}

//遍历vlaue,改成utf-8编码
for(Entry<String, String[]> entry : map.entrySet()){
//取数组值
String[] values = entry.getValue();
for(int i=0;i<values.length;i++){
try {
values[i] = new String(values[i].getBytes("ISO-8859-1"),"UTF-8");
} catch (UnsupportedEncodingException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
isEncoding = true;

return map;
}
}

三、EasyUI

jQuery EasyUI 是一个基于 jQuery 的框架,集成了各种用户界面插件。

1. JQuery第三方框架的使用

  1. 下载
  2. 查看demo
  3. 查看源码
  4. 使用源码,修改源码

2. layout布局

3. accordion折叠选项卡

4. tabs选项卡

tabs-tool动态添加选项卡

四、ztree树形插件

ztree是一个jQuery插件

1. 标准JSON数据

  1. 一个Under List标签
1
<ul id="tree" class="ztree"></ul>
  1. 配置ztree参数
1
2
var setting = {};
// 默认参数
  1. 标准json数据
1
2
3
4
5
6
7
8
9
var zNodes = [
{name:"节点一",children:[
{name:"二级节点一"},
{name:"二级节点二"},
{name:"二级节点三"}
]},
{name:"节点二"},
{name:"节点三"}
];
  1. 初始化ztree生成树
1
2
3
$(document).ready(function () {
$.fn.zTree.init($("#tree"), setting, zNodes);
});

2. 简单JSON数据

  1. 配置ztree数据
1
2
3
4
5
6
7
8
9
10
var setting = {
check: {
enable: true
},
data: {
simpleData: {
enable: true
}
}
};
  1. 简单json数据
1
2
3
4
5
6
7
8
var nodes = [
{id:1, pId:0, name: "用户功能"},
{id:11, pId:1, name: "添加用户"},
{id:12, pId:1, name: "删除用户"},
{id:2, pId:0, name: "订单功能"},
{id:12, pId:2, name: "添加订单"},
{id:22, pId:2, name: "删除订单"}
];

3. 发送ajax请求获取菜单数据

有时候树形菜单的数据是从数据库中获取的,拼接为JSON数据返回客户端,客户端使用ztree便获取树形菜单。将一个包含json数据的目录拷贝到web目录下,模拟从数据库中获取的数据。

1
2
3
4
5
6
7
8
9
10
11
// 文档加载结束,发送异步请求,获取json数据,生成树形菜单

$(document).ready(function(){
//1.获取菜单数据
var url = "${pageContext.request.contextPath}/json/menu.json"
$.get(url,function (data) {
//console.log(data);
$.fn.zTree.init($("#treeDemo"), setting, data);
})
//
});

4. 监听菜单点击事件

点击属性菜单,便新增一选项卡,在setting中可以设置:

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
var setting = {
data: {
simpleData: {
enable:true,
idKey: "id",
pIdKey: "pId",
rootPId: ""
}
},
callback:{
onClick:function (event, treeId, treeNode, clickFlag) {
// 父节点不需要显示选显卡
if (treeNode.isParent) return;

// 判断是否已经有相应的选项卡,如果有就变为选中状态,否则就显示一个新的选项卡
var tab = $("#tt").tabs("exists", treeNode.name);
if (tab) {
// 如果已经有对应的选项卡,设置为选中状态
$("#tt").tabs("select", treeNode.name);
} else {
// 否则就新增选项开
// 显示一个新的选项卡
$('#tt').tabs('add',{
title: treeNode.name,
content: '<div>'+treeNode.name+'</div>',
closable: true
});
} }
}
};

一、Servlet+Spring

1. 导入jar包

2. 在web.xml中配置Spring配置文件监听

如果在每个业务方法中都是通过 new ClassPathXmlApplicationContext("applicationContext.xml")的方法加载Spring配置文件,获取applicationContext,那么每个方法每次执行都会加载一次Spring的配置文件applicationContext.xml。但是,项目启动后Spring的配置文件应该只需加载一次到内存即可,可以在web.xml中配置一个Spring监听,项目启动就加载一次到内存,之后需要使用便获取即可。

1
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
  1. web.xml配置Spring监听
1
2
3
4
5
6
7
8
9
10
<!--Spring的监听器-->
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>

<!--Spring配置文件的路径-->
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:applicationContext.xml</param-value>
</context-param>
  1. 在需要的时候获取ApplicationContext
1
2
3
4
5
// 从容器中获取UserService
ApplicationContext context = WebApplicationContextUtils.getWebApplicationContext(this.getServletContext());

System.out.println(context.hashCode());
userService = (UserService) context.getBean("userService");

二、Struts+Spring+Hibernate

1. 导入三个框架的jar包

2. Spring配置文件

applicationContext.xml

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
<?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:tx="http://www.springframework.org/schema/tx"
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/tx
http://www.springframework.org/schema/tx/spring-tx.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd">

<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
<property name="driverClass" value="com.mysql.jdbc.Driver"/>
<property name="jdbcUrl" value="jdbc:mysql://localhost:3306/spring"/>
<property name="user" value="root"/>
<property name="password" value="root"/>
</bean>

<!--SessionFactory,读取hibernate的配置文件hibernate.cfg.xml-->
<bean id="sessionFactory" class="org.springframework.orm.hibernate3.LocalSessionFactoryBean">
<!--<property name="configLocation" value="classpath:hibernate.cfg.xml"/>-->
<!--引入hibernate.cfg.xml的内容,用以删除这个文件-->
<property name="dataSource" ref="dataSource"/>
<!--
<property name="show_sql">true</property>
<property name="format_sql">true</property>
<property name="hibernate.connection.autocommit">true</property>
<property name="hibernate.hbm2ddl.auto">update</property>
<mapping resource="cn/zhuobo/ssh/model/User.hbm.xml"/>
-->
<property name="hibernateProperties">
<props>
<prop key="hibernate.show_sql">true</prop>
<prop key="hibernate.format_sql">true</prop>
<prop key="hibernate.hbm2ddl.auto">update</prop>
<prop key="hibernate.connection.autocommit">true</prop>
</props>
</property>
<!--映射文件-->
<property name="mappingLocations" value="classpath:cn/zhuobo/ssh/model/*.hbm.xml"/>
</bean>


<!--HibernateTemplate-->
<bean id="template" class="org.springframework.orm.hibernate3.HibernateTemplate">
<property name="sessionFactory" ref="sessionFactory"/>
</bean>

<!--UserDao-->
<bean id="userDao" class="cn.zhuobo.ssh.dao.impl.UserDaoImpl">
<property name="template" ref="template"/>
</bean>

<!--UserService-->
<bean id="userService" class="cn.zhuobo.ssh.service.impl.UserServiceImpl">
<property name="userDao" ref="userDao"/>
</bean>

<!--配置事务:
1. 配置事务管理器
2. 配置一个通知
3. 配置切入点和通知结合
-->
<bean id="txManager" class="org.springframework.orm.hibernate3.HibernateTransactionManager">
<property name="sessionFactory" ref="sessionFactory"/>
</bean>

<tx:advice id="txAdvice" transaction-manager="txManager">
<tx:attributes>
<tx:method name="register"/>
</tx:attributes>
</tx:advice>

<aop:config>
<aop:pointcut id="myPointcut" expression="execution(* cn.zhuobo.ssh.service.impl.*.*(..))"/>
<aop:advisor advice-ref="txAdvice" pointcut-ref="myPointcut"/>
</aop:config>

</beans>

3. 测试文件

1
2
3
4
5
6
7
8
9
10
11
12
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = "classpath:applicationContext.xml")
public class Day01 {
@Autowired
private IUserService userService;

@Test
public void test01() {
User user = new User("Hugh1", "password1", 18);
userService.register(user);
}
}

一、Spring介绍

Spring是一个轻量级的控制反转(IoC)和面向切面(AOP)的容器框架。

轻量级:

控制反转:对象的控制权反转为Spring容器控制;

面向切面:

容器框架:Spring就是一个容器,一个管理bean的工厂,org.springframework.context.ApplicationContext接口用于完成容器的配置,初始化,管理bean。一个Spring容器就是某个实现了ApplicationContext接口的类的实例。也就是说,从代码层面,Spring容器其实就是一个ApplicationContext。

二、Spring简单入门

1. 导入Spring的核心jar包

核心jar包
spring-core-3.2.2.RELEASE.jar
包含Spring框架基本的核心工具类,Spring其它组件要都要使用到这个包里的类,是其它组件的基本核心。
spring-beans-3.2.2.RELEASE.jar
所有应用都要用到的,它包含访问配置文件、创建和管理bean,以及进行Inversion of Control(IoC) / DependencyInjection(DI)操作相关的所有类
spring-context-3.2.2.RELEASE.jar
Spring提供在基础IoC功能上的扩展服务,此外还提供许多企业级服务的支持,如邮件服务、任务调度、JNDI定位、EJB集成、远程访问、缓存以及各种视图层框架的封装等。
spring-expression-3.2.2.RELEASE.jar
Spring表达式语言
com.springsource.org.apache.commons.logging-1.1.1.jar
第三方的主要用于处理日志

2. 在使用spring之前

创建xxxService,并由xxxService调用相应的业务方法完成工作,而且每次创建一个XXXService都是一个新的对象

1
2
3
4
5
6
7
8
9
10
11
12
@Test
public void test01() {
// Spring之前,service的用法,new一个service,调动它的方法
UserService userService1 = new UserServiceImpl();
userService1.save();
UserServiceImpl userService2 = new UserServiceImpl();
userService2.save();

// 两次创建的service是不同的对象
System.out.println(userService1.hashCode()); // 1586270964
System.out.println(userService2.hashCode()); // 1642360923
}

3. Spring配置文件beans.xml

src目录下创建配置未见beans.xml,配置文件的约束在Spring文档的文件内找到:

beans.xml

1
2
3
4
5
6
7
8
9
<?xml version="1.0" encoding="UTF-8"?>
<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(对象)-->
<bean id="userService" class="cn.zhuobo.spring.service.serviceImpl.UserServiceImpl" />
</beans>

4. 获取对象

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@Test
public void test02() {
// 1. 加载配置文件beans.xml
ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");

// 2. 根据配置文件配置的id获取对象
UserService userService1 = (UserService) context.getBean("userService");
userService1.save();
UserService userService2 = (UserService) context.getBean("userService");
userService2.save();

// 从spring容器中获取对象,两次获取的是同一个对象
System.out.println(userService1.hashCode()); // 1604125387
System.out.println(userService2.hashCode()); // 1604125387
}

三、控制反转和依赖注入

1. IoC(Inversion of Control)

控制反转不是一种技术,而是一种设计思想,意味着对象的控制权被交给IoC容器,而不是传统的直接在对象内部控制。传统上在对象内部直接new另一个对象,并由程序主动地创建依赖对象,维护对象间的依赖关系(比如Customer对象有一个依赖对象Order,有应用程序类创建这两个对象,并主动将Order注入(set)到Customer)。在IoC中,所有的对象由IoC容器控制,在系统运行到适当的时候,将对象交给需要的另一个对象。所有的对象都由IoC容器控制,不再是应用程序,IoC容器控制着对象的创建、销毁,对象不再管理那些被引用的对象的生命周期。应用程序(或者一个对象)不再主动创建对象,而是被动地接受依赖对象。

传统:

控制反转:

2. DI(Dependency Injection)

DI即依赖注入,组件之间的依赖关系有IoC容器在运行之间决定,也就是应用程序需要的资源不再自己创建,而是有IoC容器创建并注入要需要依赖注入的组件(对象),由IoC容器为某个需要依赖注入的对象注入外部资源。“依赖注入”明确描述了“被注入对象依赖IoC容器配置依赖对象”

所谓控制反转,那么控制的什么反转了呢?其实就是:获得依赖对象的方式反转了!

依赖注入例子:

  1. 在前面的UserService.java中添加一个属性username,并提供属性的setter、getter方法;
  2. 在beans.xml配置依赖注入数据:
1
2
3
4
5
6
7
8
9
<!--配置一个bean-->
<!--Spring创建对象的原理:
1. 解析xml,获取id,类名,属性
2. 通过反射,用类名创建对象
3. 根据属性,给创建的对象注入相关资源
-->
<bean id="userService" class="cn.zhuobo.spring.service.serviceImpl.UserServiceImpl" >
<property name="username" value="tanzhuobo" />
</bean>

在这个例子里就是spring自己创建了一个字符串对象username,并主动注入到需要username这个对象的UserService对象中。

四、加载Spring容器的方式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
/**
* Spring容器加载的两种方式
*/
@Test
public void test01() {
// 第一种凡是:ClassPathXmlApplicationContext,是最常用的方式
ApplicationContext context1 = new ClassPathXmlApplicationContext("beans.xml");
UserService userService1 = (UserService) context1.getBean("userService");
userService1.save();

// 第二种方式:文件系统路径(绝对路径)
ApplicationContext context2 = new FileSystemXmlApplicationContext("D:\\javaProject\\spring\\src\\beans.xml");
UserService userService2 = (UserService) context2.getBean("userService");
userService2.save();

}

五、装配bean的三种方式

1. 所谓的装配bean,就是在beans.xml中配置一个bean标签

  1. 使用构造方法创建bean
1
2
3
<bean id="userService" class="cn.zhuobo.spring.service.serviceImpl.UserServiceImpl" >
<property name="username" value="tanzhuobo" />
</bean>
  1. 使用静态工厂创建bean

    写一个静态工厂,StaticUserServiceFactory.java,用来创建bean

1
2
3
4
5
6
public class StaticUserServiceFactory {

public static UserService getUserService() {
return new UserServiceImpl();
}
}

​ 在beans.xml装配一个bean

1
2
3
<!--第二种装配bean方法:使用静态工厂
指定静态工厂、以及方法-->
<bean id="userService2" class="cn.zhuobo.spring.service.StaticUserServiceFactory" factory-method="getUserService" />
  1. 使用实例工厂

    也就是创建一个工厂,但是工厂提供的创建bean的方式不是静态的,InstanceUserServiceFactory.java

1
2
3
4
5
6
public class InstanceUserServiceFactory {

public UserService getUserService() {
return new UserServiceImpl();
}
}

​ 在beans.xml中装配一个bean

1
2
3
4
5
6
7
8
<!--第三种方式:使用实例工厂
1. 先装配一个实例工厂(由于方法是非静态的,要先创建工厂对象)
2. 再装配工厂对象可以产生的bean
-->
<bean id="factory" class="cn.zhuobo.spring.service.InstanceUserServiceFactory" />
<bean id="userService3" factory-bean="factory" factory-method="getUserService">
<property name="username" value="tan3" />
</bean>

2. bean的作用域

在bean标签内可以配置scope属性,表示该bean在容器内是单例还是多例的。

说明
singleton 在Spring IoC容器中仅存在一个Bean实例,Bean以单例方式存在,默认值
prototype 每次从容器中调用Bean时,都返回一个新的实例,即每次调用getBean()时 ,相当于执行new XxxBean()

六、bean的生命周期

七、依赖注入

依赖注入,为bean(被注入对象)创建依赖对象,配置依赖对象(可以狭义的解释为为bean的属性赋值)。

1. 手动装配,使用xml

1. 构造方法注入

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<!--根据第一个构造方法依赖注入-->
<bean id="student1" class="cn.zhuobo.spring.domain.Student">
<constructor-arg name="name" value="zhuobo1" />
<constructor-arg name="age" value="18" />
</bean>

<!--根据第二个构造方法依赖注入-->
<bean id="student2" class="cn.zhuobo.spring.domain.Student">
<constructor-arg name="name" value="zhuobo2" />
<constructor-arg name="password" value="password2" />
</bean>

<!--通过索引+数据类型使用构造方法依赖注入-->
<bean id="student3" class="cn.zhuobo.spring.domain.Student">
<constructor-arg index="0" type="java.lang.String" value="zhuobo3"/>
<constructor-arg index="1" type="java.lang.Integer" value="19" />
</bean>

2. setter方法注入

1
2
3
4
5
<bean id="student4" class="cn.zhuobo.spring.domain.Student" >
<property name="name" value="zhuobo4" />
<property name="password" value="password4" />
<property name="age" value="18" />
</bean>

3. p命名空间注入

2. Spring表达式

1
2
3
4
5
#{123}、#{'jack'} : 数字、字符串
#{beanId} :另一个bean引用
#{beanId.propName} :操作数据
#{beanId.toString()} :执行方法
#{T(类).字段|方法} :静态方法或字段

3. 集合注入

集合的注入都是在property添加子标签:

数组:array

Set:<set>

List:<list>

Map:<map>,使用子标签 <entry>描述

Properties:<props>,使用子标签 <prop key='key'>value</prop>描述

  1. 数组:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// 类
public class Student {
private String name;
private String[] courses;
}

// 配置一个bean,并为集合属性注入数据
<bean id="student" class="cn.zhuobo.spring.domain.Student">
<property name="name" value="zhuobo" />
<property name="courses">
<array>
<value>Math</value>
<value>English</value>
<value>Computer Science</value>
</array>
</property>
</bean>
  1. List
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// class
public class Student {
private String name;
private List<String> courses;
}

// 配置一个bean,并为集合属性注入数据
<bean id="student" class="cn.zhuobo.spring.domain.Student">
<property name="name" value="zhuobo" />
<property name="courses">
<list>
<value>Math</value>
<value>English</value>
<value>Computer Science</value>
</list>
</property>
</bean>
  1. Set
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// class
public class Student {
private String name;
private List<String> courses;
private Set<String> cars;
}

<bean id="student" class="cn.zhuobo.spring.domain.Student">
<property name="cars">
<set>
<value>BMW</value>
<value>Audi</value>
<value>Ferrari</value>
<value>Porsche</value>
</set>
</property>
</bean>
  1. Map
1
2
3
4
5
6
7
8
9
10
11
12
13
public class Student {
private String name;
private Map<String, String> info;
}
// 为map属性注入数据
<bean id="student" class="cn.zhuobo.spring.domain.Student">
<property name="info">
<map>
<entry key="name" value="zhuobo"/>
<entry key="id" value="14332017"/>
</map>
</property>
</bean>
  1. Properties
1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class Student {
private String name;
private Properties mySqlInfos;
}
// 为map属性注入数据
<bean id="student" class="cn.zhuobo.spring.domain.Student">
<property name="mySqlInfos">
<props>
<prop key="username">zhuobo</prop>
<prop key="password">password</prop>
<prop key="url">mysql:jdbc://localhost:3306/database</prop>
</props>
</property>
</bean>

4. 注解注入

使用注解来取代xml配置,在类上添加注解就相当于在xml写了一个手动装配了一个bean标签,Spring的注解注入默认是不开启的,开启注解注入如下:

  1. @Component:没有配置id,需要根据类型获取;
1
2
3
// 如果@Component没有配置id,那么就可以使用getBean通过类型去获取对象
// 参数可以是实现类类型,获取接口类型
UserService userService = (UserService) context.getBean(UserService.class);
  1. @Component(“id”):配置了id,可以使用getBean,根据参数id获取bean;

  2. web开发@Component的衍生注解:

    @Repository(“名称”):dao层

    @Service(“名称”):service层

    @Controller(“名称”):web层

@AutoWired:根据数据类型自动注入,如果是接口,spring根据类型从容器中找到合适的bean注入

@Qualifier(“名称”):指定自动注入的id名称,与@AutoWired结合使用

@Resource(“名称”):相当于上面两个注解结合使用的简写

@ PostConstruct:自定义初始化

@ PreDestroy:自定义销毁

  1. @scope(prototype):多例,默认是单例

八、AOP(面向切面)

Aspect Oriented Programming(面向切面编程),通过预编译方式和运行期动态代理实现程序功能的统一维护的一种技术。AOP实现的目的是对业务处理过程的切面进行提取,提取处理工程的某个步骤或者阶段,以获得逻辑过程各个部分之间低耦合性的隔离效果。

1)AOP是OOP(面向对象编程)的延续,是软件开发中的一个热点,也是Spring框架中的一个重要内容,是函数式编程的一种衍生范型。

2)利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。

3)AOP采取横向抽取机制,取代了传统纵向继承体系重复性代码

4) 经典应用:事务管理、性能监视、安全检查、缓存、日志等

5) Spring AOP使用纯Java实现,不需要专门的编译过程和类加载器,在运行期通过代理方式向目标类织入增强代码

1. AOP实现原理

a.aop底层将采用代理机制进行实现。

b.接口 + 实现类 :spring采用 jdk 的动态代理Proxy。

c. 实现类:spring 采用 cglib字节码增强。

2. AOP术语

1.target:目标类,需要被代理的类。例如:UserService
2.Joinpoint(连接点):所谓连接点是指那些可能被拦截到的方法。例如:所有的方法
3.PointCut 切入点:已经被增强的连接点。例如:addUser()
4.advice 通知/增强,增强代码。例如:after、before
5.Weaving(织入):是指把增强advice应用到目标对象target来创建新的代理对象proxy的过程.
6.proxy 代理类
7.Aspect(切面): 是切入点pointcut和通知advice的结合
一个线是一个特殊的面。
一个切入点和一个通知,组成成一个特殊的面。

3. 手动代理

1. JDK动态代理

这种方法需要目标类有接口以及实现类

目标类:

切面类:

工厂类:创建一个工厂类,工厂类的静态方法返回一个UserService接口,通过创建代理对象的方法,返回一个代理对象

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
public class MyBeanFactory {
public static UserService createUserService() {
// 1. 创建目标对象
final UserService userService = new UserServiceImpl();
// 2. 生命切面类对象
final MyAspect myAspect = new MyAspect();

// 3. 把切面的两个方法应用到目标对象
// 3.1 创建jdk代理
UserService userServiceProxy = (UserService) Proxy.newProxyInstance(
MyBeanFactory.class.getClassLoader(),
userService.getClass().getInterfaces(),
new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// 执行增强代码
myAspect.before();

// 执行目标对象的方法,并传递参数
Object object = method.invoke(userService, args);

// 执行增强方法
myAspect.after();

return object;
}
});
// 返回的是代理对象
return userServiceProxy;
}
}

测试:

2. cglib增强字节码

  1. 目标对象只有类,没有接口(目标对象没有实现接口),或者有接口由实现类都可以使用cglib字节码增强框架增强对象;
  2. 使用cglib字节码增强框架,在运行时创建目标类的子类,从而对目标类进行增强。

导入jar包:spring-core.jar整合了cglib字节码增强框架;

工厂类:

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
public static UserService createUserService1() {
// 1. 创建目标对象
final UserService userService = new UserServiceImpl();
// 2. 生命切面类对象
final MyAspect myAspect = new MyAspect();

// 3. 把切面的两个方法应用到目标对象

// 创建增强对象
Enhancer enhancer = new Enhancer();
// 设置增强对象的父类,也就是要被增强的对象
enhancer.setSuperclass(userService.getClass());

enhancer.setCallback(new MethodInterceptor() {
@Override
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {

myAspect.before();

// Object obj = method.invoke(userService, objects);
// 或者这样子执行方法
Object obj = methodProxy.invokeSuper(o, objects);
myAspect.after();

return obj;

}
});
UserService userServiceProxy = (UserService) enhancer.create();
return userServiceProxy;
}

4. AOP的5中通知类型

AOP联盟为通知Advice定义了org.aopalliance.aop.Advice

Spring按照通知Advice在目标类方法的连接点位置,可以分为5类

​ •前置通知 org.springframework.aop.MethodBeforeAdvice

​ •在目标方法执行前实施增强

​ •后置通知 org.springframework.aop.AfterReturningAdvice

​ •在目标方法执行后实施增强

​ •环绕通知 org.aopalliance.intercept.MethodInterceptor

​ •在目标方法执行前后实施增强

​ •异常抛出通知 org.springframework.aop.ThrowsAdvice

​ •在方法抛出异常后实施增强

​ •引介通知 org.springframework.aop.IntroductionInterceptor

​ 在目标类中添加一些新的方法和属性

5. Spring编写半自动代理

1. 导入jar包

除了Spring的5个核心jar包之外,还需要导入2个jar包,分别是aop联盟、aop实现:

2. 目标类

3. 切面类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class MyAspect implements MethodInterceptor {

@Override
public Object invoke(MethodInvocation methodInvocation) throws Throwable {

// 执行方法前的操作before advice
System.out.println("--start transaction--");

// 拦截方法
Object object = methodInvocation.proceed();

// 执行方法后的操作 after advice
System.out.println("--commit transaction--");

return object;
}
}

4. spring配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<!--业务类-->
<bean id="userService" class="cn.zhuobo.spring.service.serviceImpl.UserServiceImpl" />
<!--切面类-->
<bean id="myAspect" class="cn.zhuobo.spring.service.MyAspect" />

<!--代理对象-->
<bean id="userServiceProxy" class="org.springframework.aop.framework.ProxyFactoryBean">
<!--业务类实现的接口,如有多个用array表示-->
<property name="interfaces" value="cn.zhuobo.spring.service.UserService"/>
<!--目标,也就是目标对象,业务类-->
<property name="target" ref="userService"/>
<!--切面类,多个用array表示-->
<property name="interceptorNames" value="myAspect" />

<!--配置是使用JDK动态代理还是cglib代理
默认是JDK动态代理,值为true表示使用cglib代理
-->
<property name="optimize" value="true" />
</bean>

5. 测试

1
2
3
4
5
6
7
@Test
public void test07() {
ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
// 注意到获取的是代理对象
UserService userServiceProxy = (UserService) context.getBean("userServiceProxy");
userServiceProxy.deleteUser();
}

6. Spring全自动AOP

1. 导入jar包

1
spring-framework-3.0.2.RELEASE-dependencies\org.aspectj\com.springsource.org.aspectj.weaver\1.6.8.RELEASE.jar

2. 配置xml

3. 测试

注意到,这种配置方法是不会出现代理对象的,虽然说实际上还是代理对象在工作,根据 proxy-target-class配置使用什么代理对象。

1
2
3
4
5
6
7
8
@Test
public void test07() {
ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");

// 注意到与前面的半自动不同,这里不是获取代理对象,而是直接获取目标对象
UserService userServiceProxy = (UserService) context.getBean("userService");
userServiceProxy.deleteUser();
}

九、AspectJ

1. 介绍

  1. AspectJ是一个基于Java语言的AOP框架

  2. Spring2.0以后新增了对AspectJ切点表达式支持

  3. @AspectJ 是AspectJ1.5新增功能,通过JDK5注解技术,允许直接在Bean类中定义切面

  4. 新版本Spring框架,建议使用AspectJ方式来开发AOP

  5. 主要用途:自定义开发

2. 切入点表达式

切入点表达式用于描述作为切入点的方法

  1. Execution()
    语法:execution(修饰符 返回值 包.类.方法名(参数) throws异常)

    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
    修饰符,一般省略
    public 公共方法
    * 任意

    返回值,不能省略
    void 返回没有值
    String 返回值字符串
    * 任意

    包,[省略]
    com.gyf.crm 固定包
    com.gyf.crm.*.service crm包下面子包任意 (例如:com.gyf.crm.staff.service)
    com.gyf.crm.. crm包下面的所有子包(含自己)
    com.gyf.crm.*.service.. crm包下面任意子包,固定目录service,service目录任意包

    类,[省略]
    UserServiceImpl 指定类
    *Impl 以Impl结尾
    User* 以User开头
    * 任意

    方法名,不能省略
    addUser 固定方法
    add* 以add开头
    *Do 以Do结尾
    * 任意

    (参数)
    () 无参
    (int) 一个整型
    (int ,int) 两个
    (..) 参数任意

    throws ,可省略,一般不写。
  2. within():匹配包或者子包内的方法

    语法:within(com.zhuobo.service..*)

  3. this():匹配实现接口的代理对象中的方法

    语法:this(com.zhuobo.aop.user.UserDao)

  4. target():匹配实现接口的目标对象中的方法

    语法:this(com.zhuobo.aop.user.UserDao)

  5. args():匹配参数格式符合指定格式的方法

    语法:args(int, int )

  6. bean():指定的bean中的所有方法

    语法:bean(‘bean id’)

3. AspectJ通知类型

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
aop联盟定义通知类型,具有特性接口,必须实现,从而确定方法名称。
aspectj 通知类型,只定义类型名称,以及方法格式。
个数:6种,知道5种,掌握1中。
before:前置通知(应用:各种校验)
在方法执行前执行,如果通知抛出异常,阻止方法运行
afterReturning:后置通知(应用:常规数据处理)
方法正常返回后执行,如果方法中抛出异常,通知无法执行
必须在方法执行后才执行,所以可以获得方法的返回值。
around:环绕通知(应用:十分强大,可以做任何事情)
方法执行前后分别执行,可以阻止方法的执行
必须手动执行目标方法
afterThrowing:抛出异常通知(应用:包装异常信息)
方法抛出异常后执行,如果方法没有抛出异常,无法执行
after:最终通知(应用:清理现场)
方法执行完毕后执行,无论方法中是否出现异常

4. 基于xml的AspectJ的使用

1. 导入包

2. 编写业务类和切面类

业务类:

切面类:

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
public class MyAspect{

public void myBefore(JoinPoint joinPoint) {
System.out.println(joinPoint.getSignature().getName());
System.out.println("--BEFORE ADVICE--");
}

public void myAfter(JoinPoint joinPoint) {
System.out.println(joinPoint.getSignature().getName());
System.out.println("--AFTER ADVICE--");
}

public Object myAround(ProceedingJoinPoint joinPoint) throws Throwable {
System.out.println(joinPoint.getSignature().getName());
System.out.println("--before around ADVICE--");
Object object = joinPoint.proceed();
System.out.println("--after around ADVICE--");

return object;
}

public void myAfterReturning(JoinPoint joinPoint) {
System.out.println(joinPoint.getSignature().getName());
System.out.println("--myAfterReturning ADVICE--");
}

}

3. Spring的xml配置

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
<!--业务类-->
<bean id="userService" class="cn.zhuobo.spring.service.serviceImpl.UserServiceImpl" />
<!--切面类-->
<bean id="myAspect" class="cn.zhuobo.spring.service.MyAspect" />

<aop:config proxy-target-class="true">
<aop:aspect ref="myAspect">
<aop:pointcut id="myPointcut" expression="execution(* cn.zhuobo.spring.service.serviceImpl.UserServiceImpl.*(..))"/>

<!--前置通知-->
<aop:before method="myBefore" pointcut-ref="myPointcut" />

<!--后置通知:有异常就不会执行,可以返回目标对象的返回值-->
<aop:after-returning returning="returning" method="myAfterReturning" pointcut-ref="myPointcut" />

<!--相当于前置 + 后置通知-->
<aop:around method="myAround" pointcut-ref="myPointcut" />

<!--异常通知-->
<aop:after-throwing method="myAfterThrowing" pointcut-ref="myPointcut" throwing="throwable" />

<!--最终通知:有无异常都会执行-->
<aop:after method="myAfter" pointcut-ref="myPointcut" />
</aop:aspect>
</aop:config>

4. 测试

1
2
3
4
5
6
7
8
@Test
public void test01() {
ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");

// 直接根据ID获取bean,默认也是使用jdk实现的代理,而非使用cglib代理
UserService userService = (UserService) context.getBean("userService");
userService.deleteUser();
}

5. 给予注解的AspectJ的使用

1. 声明使用注解

1
2
3
4
<!--配置:扫描注解的包的范围-->
<context:component-scan base-package="cn.zhuobo.spring" />
<!--配置:使用注解注入-->
<aop:aspectj-autoproxy />

2. 使用注解配置业务类和切面类

原来的xml配置方式:

1
2
3
4
<!--业务类-->
<bean id="userService" class="cn.zhuobo.spring.service.serviceImpl.UserServiceImpl" />
<!--切面类-->
<bean id="myAspect" class="cn.zhuobo.spring.service.MyAspect" />

使用注解:

1
2
3
4
5
6
@Service("userService")
public class UserServiceImpl implements UserService

@Component
@Aspect // 使用@Aspect注解声明这是一个切面类
public class MyAspect{

3. 声明一个公共的切入点

@PointCut ,修饰方法 private void xxx(){} 之后通过“方法名”获得切入点引用

原来的xml配置方式:

1
<aop:pointcut id="myPointcut" expression="execution(* cn.zhuobo.spring.service.serviceImpl.UserServiceImpl.*(..))"/>

使用注解:在切面类里面创建一个方法,这个方法什么都不做,就只是添加一个注解,声明是公共的切入点

十、JDBC Template

JDBC Template是用于操作JDBC(Java DataBase Connectivity)的工具类,需要使用数据库连接池技术,其中两种比较常用的数据库连接池就是微软公司提供的dbcp、以及开源的c3p0。(c3p0有自动回收空闲连接的功能,而DBCP没有自动回收空闲连接的功能。控线连接指的是指定时间内,也就是长时间不使用的连接。DBCP的回收机制是如果连接数大于最大连接数,便回收连接。)

1. 在Spring中配置c3p0连接池,使用JDBC Template

1. 导入相关jar包

2. 在spring中配置

1
2
3
4
5
6
7
8
9
10
11
12
<!--配置一个DataSource,以供jdbc template使用-->
<bean id="datasource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
<property name="driverClass" value="com.mysql.jdbc.Driver" />
<property name="password" value="root"/>
<property name="user" value="root" />
<property name="jdbcUrl" value="jdbc:mysql://localhost:3306/spring" />
</bean>

<!--配置一个jdbc template,需要一个DataSource-->
<bean id="template" class="org.springframework.jdbc.core.JdbcTemplate">
<property name="dataSource" ref="datasource"/>
</bean>

3. 测试

1
2
3
4
5
6
7
8
9
@Test
public void test04(){
ApplicationContext context = new ClassPathXmlApplicationContext("beans02.xml");
JdbcTemplate template = (JdbcTemplate) context.getBean("template");

int rows = template.update("insert into t_user values (null, ?, ?)", "zhuobo2", "password2");

System.out.println("Affect rows :"+rows);
}

4. 将配置连接池的信息独立为一个文件

  1. 在类路径src目录下创建一个文件database.properties,文件内写数据库连接信息
1
2
3
4
driverClass=com.mysql.jdbc.Driver
jdbcUrl=jdbc:mysql://localhost:3306/spring
password=root
user=root
  1. 在spring配置文件中配置
1
2
3
4
5
6
7
8
9
10
11
<!--加载database.properties文件-->
<context:property-placeholder location="classpath:database.properties" />

<!--加载database.properties文件后使用 ${driverClass} 的方式获取值-->
<!--配置一个DataSource,以供jdbc template使用-->
<bean id="datasource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
<property name="driverClass" value="${driverClass}" />
<property name="password" value="${password}"/>
<property name="user" value="${user}" />
<property name="jdbcUrl" value="${jdbcUrl}" />
</bean>

十一、事务管理

1. 事务回顾

简单事务:ABCD要么全部成功,要么全部失败;

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
ABCD 一个事务
Connection conn = null;
try{
//1 获得连接
conn = ...;
//2 开启事务
conn.setAutoCommit(false);
A
B
C
D
//3 提交事务
conn.commit();
} catche(){
//4 回滚事务
conn.rollback();
}

有保存点的事务:AB(必须),CD(可选)

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
Connection conn = null;
Savepoint savepoint = null; //保存点,记录操作的当前位置,之后可以回滚到指定的位置。(可以回滚一部分)
try{
//1 获得连接
conn = ...;
//2 开启事务
conn.setAutoCommit(false);
A
B
savepoint = conn.setSavepoint();
C
D
//3 提交事务
conn.commit();
} catche(){
if(savepoint != null){ //CD异常
// 回滚到CD之前
conn.rollback(savepoint);
// 提交AB
conn.commit();
} else{ //AB异常
// 回滚AB
conn.rollback();
}
}

2. 事务的传播行为

事务 描述
PROPAGATION_REQUIRED required , 必须 【默认值】 支持当前事务,A如果有事务,B将使用该事务。 如果A没有事务,B将创建一个新的事务。
PROPAGATION_SUPPORTS supports ,支持 支持当前事务,A如果有事务,B将使用该事务。 如果A没有事务,B将以非事务执行。
PROPAGATION_MANDATORY mandatory ,强制 支持当前事务,A如果有事务,B将使用该事务。 如果A没有事务,B将抛异常。
PROPAGATION_REQUIRES_NEW requires_new ,必须新的 如果A有事务,将A的事务挂起,B创建一个新的事务 如果A没有事务,B创建一个新的事务
PROPAGATION_NOT_SUPPORTED not_supported ,不支持 如果A有事务,将A的事务挂起,B将以非事务执行 如果A没有事务,B将以非事务执行
PROPAGATION_NEVER never,从不 如果A有事务,B将抛异常 如果A没有事务,B将以非事务执行
PROPAGATION_NESTED nested ,嵌套 A和B底层采用保存点机制,形成嵌套事务。

3. 手动管理事务

Spring底层使用TransactionTemplate对事务进行管理,还是比较麻烦的,可以直接使用TransactionTemplate,或者使用TransactionProxyFactoryBean生成代理管理事务,具体操作过程:

  1. Service提供一个TransactionTemplate属性
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
public class AccountServiceImpl implements AccountService {

private AcountDao accountDao; // 使用spring注入
private TransactionTemplate transactionTemplate; // 事务模板,手动使用事务

public void setTransactionTemplate(TransactionTemplate transactionTemplate) {
this.transactionTemplate = transactionTemplate;
}

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

@Override
public void transfer(final String outer, final String inner, final Integer money) {
this.transactionTemplate.execute(new TransactionCallbackWithoutResult() {
@Override
protected void doInTransactionWithoutResult(TransactionStatus transactionStatus) {
accountDao.out(outer, money);
int num = 1/0; // 一个异常,出现异常这里的所有代码是一个事务,会回滚
accountDao.in(inner, money);
}
});
}
}
  1. 配置xml
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
<!--加载database.properties文件-->
<context:property-placeholder location="classpath:database.properties" />

<!--加载database.properties文件后使用 ${driverClass} 的方式获取值-->
<!--配置一个DataSource,以供jdbc template使用-->
<bean id="datasource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
<property name="driverClass" value="${driverClass}" />
<property name="password" value="${password}"/>
<property name="user" value="${user}" />
<property name="jdbcUrl" value="${jdbcUrl}" />
</bean>

<!--配置一个dao,AccountDao继承了JdbcDaoSupport,提供DataSource即可注入,获取JDBCTemplate-->
<bean id="accountDao" class="cn.zhuobo.spring.dao.daoImpl.AcountDaoImpl">
<property name="dataSource" ref="datasource"/>
</bean>

<!--事务管理器,因为事务模板(TransactionTemplate)需要一个事务管理器属性-->
<bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="datasource" />
</bean>

<bean id="transactionTemplate" class="org.springframework.transaction.support.TransactionTemplate">
<!--需要一个事务管理器-->
<property name="transactionManager" ref="txManager"/>
</bean>

<!--配置一个service-->
<bean id="accountService" class="cn.zhuobo.spring.service.serviceImpl.AccountServiceImpl">
<property name="accountDao" ref="accountDao" />
<property name="transactionTemplate" ref="transactionTemplate" />
</bean>

4. 基于AOP的事务管理

1. 基于xml的配置

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
<context:property-placeholder location="classpath:database.properties" />

<!--配置一个DataSource,以供jdbc template使用-->
<bean id="datasource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
<property name="driverClass" value="${driverClass}" />
<property name="password" value="${password}"/>
<property name="user" value="${user}" />
<property name="jdbcUrl" value="${jdbcUrl}" />
</bean>

<!--配置一个dao-->
<bean id="accountDao" class="cn.zhuobo.spring.dao.daoImpl.AcountDaoImpl">
<property name="dataSource" ref="datasource"/>
</bean>

<!--service-->
<!--配置一个service-->
<bean id="accountService" class="cn.zhuobo.spring.service.serviceImpl.AccountServiceImpl">
<property name="accountDao" ref="accountDao" />
</bean>

<!--事务管理器-->
<bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="datasource" />
</bean>

<!--基于AOP的事务配置:
1. 配置一个通知
2. 将通知应用到切点
-->
<tx:advice id="txAdvice" transaction-manager="txManager">
<tx:attributes>
<tx:method name="transfer" propagation="REQUIRED" isolation="DEFAULT"/>
</tx:attributes>
</tx:advice>

<!--配置一个配置一个切点,以及切点应用的通知-->
<aop:config>
<aop:pointcut id="myPointcut" expression="execution(* cn.zhuobo.spring.service.serviceImpl.*.*(..))"/>
<aop:advisor advice-ref="txAdvice" pointcut-ref="myPointcut"/>
</aop:config>

AccountService

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class AccountServiceImpl implements AccountService {

private AcountDao accountDao; // 使用spring注入
// 不用提供事务模板的属性
public void setAccountDao(AcountDao accountDao) {
this.accountDao = accountDao;
}

@Override
public void transfer(String outer, String inner, Integer money) {
accountDao.out(outer, money);
int num = 1 / 0; // 一个异常
accountDao.in(inner, money);
}
}

2. 注解配置事务

  1. 首先要先开启使用注解配置事务
  1. 在业务类上或者具体的业务类方法上使用注解

一、概念

Redis是一款高性能的NOSQL系列(NoSQL = Not Only SQL )的数据库

1.1.什么是NOSQL

​ NoSQL(NoSQL = Not Only SQL),意即“不仅仅是SQL”,是一项全新的数据库理念,泛指非关系型的数据库。
​ 随着互联网web2.0网站的兴起,传统的关系数据库在应付web2.0网站,特别是超大规模和高并发的SNS类型的web2.0纯动态网站已经显得力不从心,暴露了很多难以克服的问题,而非关系型的数据库则由于其本身的特点得到了非常迅速的发展。NoSQL数据库的产生就是为了解决大规模数据集合多重数据种类带来的挑战,尤其是大数据应用难题。

​ 1.1.1. NOSQL和关系型数据库比较
​ 优点:
​ 1)成本:nosql数据库简单易部署,基本都是开源软件,不需要像使用oracle那样花费大量成本购买使用,相比关系型数据库价格便宜。
​ 2)查询速度:nosql数据库将数据存储于缓存之中,关系型数据库将数据存储在硬盘中,自然查询速度远不及nosql数据库。
​ 3)存储数据的格式:nosql的存储格式是key,value形式、文档形式、图片形式等等,所以可以存储基础类型以及对象或者是集合等各种格式,而数据库则只支持基础类型。
​ 4)扩展性:关系型数据库有类似join这样的多表查询机制的限制导致扩展很艰难。

​ 缺点:
​ 1)维护的工具和资料有限,因为nosql是属于新的技术,不能和关系型数据库10几年的技术同日而语。
​ 2)不提供对sql的支持,如果不支持sql这样的工业标准,将产生一定用户的学习和使用成本。
​ 3)不提供关系型数据库对事务的处理。

​ 1.1.2. 非关系型数据库的优势:

​ 1)性能NOSQL是基于键值对的,可以想象成表中的主键和值的对应关系,而且不需要经过SQL层的解析,所以性能非常高。
​ 2)可扩展性同样也是因为基于键值对,数据之间没有耦合性,所以非常容易水平扩展。

​ 1.1.3. 关系型数据库的优势:
​ 1)复杂查询可以用SQL语句方便的在一个表以及多个表之间做非常复杂的数据查询。
​ 2)事务支持使得对于安全性能很高的数据访问要求得以实现。对于这两类数据库,对方的优势就是自己的弱势,反之亦然。

​ 1.1.4. 总结
​ 关系型数据库与NoSQL数据库并非对立而是互补的关系,即通常情况下使用关系型数据库,在适合使用NoSQL的时候使用NoSQL数据库,
​ 让NoSQL数据库对关系型数据库的不足进行弥补。
一般会将数据存储在关系型数据库中,在nosql数据库中备份存储关系型数据库的数据

1.2.主流的NOSQL产品

​ • 键值(Key-Value)存储数据库
​ 相关产品: Tokyo Cabinet/Tyrant、Redis、Voldemort、Berkeley DB
​ 典型应用: 内容缓存,主要用于处理大量数据的高访问负载。
​ 数据模型: 一系列键值对
​ 优势: 快速查询
​ 劣势: 存储的数据缺少结构化
​ • 列存储数据库
​ 相关产品:Cassandra, HBase, Riak
​ 典型应用:分布式的文件系统
​ 数据模型:以列簇式存储,将同一列数据存在一起
​ 优势:查找速度快,可扩展性强,更容易进行分布式扩展
​ 劣势:功能相对局限
​ • 文档型数据库
​ 相关产品:CouchDB、MongoDB
​ 典型应用:Web应用(与Key-Value类似,Value是结构化的)
​ 数据模型: 一系列键值对
​ 优势:数据结构要求不严格
​ 劣势: 查询性能不高,而且缺乏统一的查询语法
​ • 图形(Graph)数据库
​ 相关数据库:Neo4J、InfoGrid、Infinite Graph
​ 典型应用:社交网络
​ 数据模型:图结构
​ 优势:利用图结构相关算法。
​ 劣势:需要对整个图做计算才能得出结果,不容易做分布式的集群方案。

1.3 什么是Redis

​ Redis是用C语言开发的一个开源的高性能键值对(key-value)数据库,官方提供测试数据,50个并发执行100000个请求,读的速度是110000次/s,写的速度是81000次/s ,且Redis通过提供多种键值数据类型来适应不同场景下的存储需求,目前为止Redis支持的键值数据类型如下:
​ 1) 字符串类型 string
​ 2) 哈希类型 hash
​ 3) 列表类型 list
​ 4) 集合类型 set
​ 5) 有序集合类型 sortedset
​ 1.3.1 redis的应用场景
​ • 缓存(数据查询、短连接、新闻内容、商品内容等等)
​ • 聊天室的在线好友列表
​ • 任务队列。(秒杀、抢购、12306等等)
​ • 应用排行榜
​ • 网站访问统计
​ • 数据过期处理(可以精确到毫秒
​ • 分布式集群架构中的session分离

二、命令操作

1. Redis的数据结构

Redis存储的是键值对,key都是字符串,value可以有5种不同的类型数据:

  1. 字符串 string
  2. 哈希类型 hash:map格式,可以存储键值对
  3. 列表类型 list:linkedlist格式,元素可以重复
  4. 集合类型 set 元素不可以重复
  5. 有序集合类型 sortedset 元素可以重复而且有序

2. Redis命令操作

  1. 字符串string

    1. 存储:set key value
    2. 获取:get key
    3. 删除:del key
  2. 哈希类型 hash

    1. 存储:hset key field value

    2. 获取:

      1. hget key field:获取指定的field对应的值
      2. hgetall key:获取所有的field和value
    3. 删除:hdel key field

      1
      2
      3
      4
      hset myhash username Tom
      hset myhash age 18

      hget myhash age -- 18
  3. 列表类型 list:可以添加一个元素到列表的头部(左)或者尾部(右)

    1. 存储:

      1. lpush key value:将元素加入列表左边
      2. rpush key value:将元素加入列表右边
    2. 获取:

      1. lrange key start end:范围获取
    3. 删除:

      1. lpop:删除最左边元素,并返回该元素
      2. rpop:删除最右边元素,并返回该元素
      1
      2
      3
      4
      5
      lpush mylist a
      lpush mylist b
      lpush mylist c -- a->b->c

      lrange mylist 0 -1 -- 获取mylist所有的元素
  4. 集合类型 set

    1. 存储:sadd key value
    2. 获取:smembers key:获取集合中所有的元素
    3. 删除:srem key value:删除集合中某个元素
  5. 有序集合类型 sortedset

    1. 存储:zadd key score value

    2. 获取:zrange key start end(withscores)

    3. 删除:zrem key value

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
       127.0.0.1:6379>zadd sorted 10 aaa
      (integer) 1
      127.0.0.1:6379> zadd sorted 30 bbb
      (integer) 1
      127.0.0.1:6379> zadd sorted 20 ccc
      (integer) 1
      127.0.0.1:6379> zrange sorted 0 -1
      // 按照分数(score)排序
      1) "aaa"
      2) "ccc"
      3) "bbb"
  6. 常用的通用命令:

    1. keys *:查询所有的键
    2. type key:获取键对应的value的类型
    3. del key:删除指定的key以及该key对应的value

三、Redis持久化

Redis是一个基于内存的数据库,因此当Redis服务器重启会导致数据丢失,可以将Redis数据库的数据存储到硬盘。

1. Redis持久化机制

  1. RDB: 默认方式,不需要进行配置,默认使用的机制,在一定的时间间隔内,检测key的变化情况,然后持久化数据

    1. 编辑配置文件:redis.windows.conf

      1
      2
      3
      4
      5
      6
      7
      #   after 900 sec (15 min) if at least 1 key changed
      # after 300 sec (5 min) if at least 10 keys changed
      # after 60 sec if at least 10000 keys changed

      save 900 1
      save 300 10
      save 60 10000
    2. 重新启动Redis服务器,并且指定配置文件:$ redis-server.exe redis.windows.conf

  1. AOF: 记录日志的方式,可以记录每一条操作的命令,操作命令后就持久化数据(这样的效果和mysql类似了)

    1. 编辑配置文件:redis.windows.conf

      1
      2
      3
      4
      5
      6
      7
      8
      # 开启aof模式
      appendonly no --> appendonly yes

      # aof模式下的三种持久化方式

      # appendfsync always # 总是,每一条操作都持久化
      appendfsync everysec # 每间隔1s进行一次持久化
      # appendfsync no # 不进行持久化

四、Java客户端Jedis

  1. Jedis是一款java操作Redis数据库的工具,类似于JDBC

  2. 使用步骤:

    1. 下载、导入jar包
    2. 使用
  3. 使用Demo

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    @Test
    public void test2() {
    // 获取连接, 如果使用的是空参数的构造,那么默认就是localhost和6379
    Jedis jedis = new Jedis("localhost", 6379);
    // 操作
    jedis.set("username", "David");

    String username = jedis.get("username");

    System.out.println(username);// David

    // 可以使用方法setex来存储指定的过期时间的key value
    jedis.setex("activeCode", 20, "didi");// 20秒后键activeCode、值didi就被自动删除
    // 关闭连接
    jedis.close();
    }
  4. Jedis连接池

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    @Test
    public void test5() {
    // 创建一个配置对象
    JedisPoolConfig config = new JedisPoolConfig();
    config.setMaxTotal(50);// 最大的允许连接数量
    config.setMaxIdle(10);// 最大的空闲连接数

    // 创建一个JedisPool连接池对象
    JedisPool jedisPool = new JedisPool(config, "localhost", 6379);

    // 获取连接
    Jedis jedis = jedisPool.getResource();

    // 使用
    jedis.set("username", "Tom");

    // 归还连接给连接池
    jedis.close();
    }