今日内容

l Service事务

l 客户关系管理系统

Service事务

  在Service中使用ThreadLocal来完成事务,为将来学习Spring事务打基础!

1 DAO中的事务

在DAO中处理事务真是“小菜一碟”。

public void xxx() {

Connection con = null;

try {

con = JdbcUtils.getConnection();

con.setAutoCommitted(false);

QueryRunner qr = new QueryRunner();

String sql = …;

Object[] params = …;

qr.update(con, sql, params);

sql = …;

Object[] params = …;

qr.update(con, sql, params);

con.commit();

} catch(Exception e) {

try {

if(con != null) {con.rollback();}

} catch(Exception e) {}

} finally {

try {

con.close();

} catch(Exception e) {}

}

}

2 Service才是处理事务的地方

  我们要清楚一件事,DAO中不是处理事务的地方,因为DAO中的每个方法都是对数据库的一次操作,而Service中的方法才是对应一个业务逻辑。也就是说我们需要在Service中的一方法中调用DAO的多个方法,而这些方法应该在一起事务中。

怎么才能让DAO的多个方法使用相同的Connection呢?方法不能再自己来获得Connection,而是由外界传递进去。

public void daoMethod1(Connection con, …) {

}

public void daoMethod2(Connection con, …) {

}

在Service中调用DAO的多个方法时,传递相同的Connection就可以了。

public class XXXService() {

private XXXDao dao = new XXXDao();

public void serviceMethod() {

Connection con = null;

try {

con = JdbcUtils.getConnection();

con.setAutoCommitted(false);

dao.daoMethod1(con, …);

dao.doaMethod2(con, …);

com.commint();

} catch(Exception e) {

try {

con.rollback();

} catch(Exception e) {}

} finally {

try {

con.close();

} catch(Exception e) {}

}

}

}

但是,在Service中不应该出现Connection,它应该只在DAO中出现,因为它是JDBC的东西,JDBC的东西是用来连接数据库的,连接数据库是DAO的事儿!!!但是,事务是Service的事儿,不能放到DAO中!!!

3 修改JdbcUtils

我们把对事务的开启和关闭放到JdbcUtils中,在Service中调用JdbcUtils的方法来完成事务的处理,但在Service中就不会再出现Connection这一“禁忌”了。

DAO中的方法不用再让Service来传递Connection了。DAO会主动从JdbcUtils中获取Connection对象,这样,JdbcUtils成为了DAO和Service的中介!

我们在JdbcUtils中添加beginTransaction()和rollbackTransaction(),以及commitTransaction()方法。这样在Service中的代码如下:

public class XXXService() {

private XXXDao dao = new XXXDao();

public void serviceMethod() {

try {

JdbcUtils.beginTransaction();

dao.daoMethod1(…);

dao.daoMethod2(…);

JdbcUtils.commitTransaction();

} catch(Exception e) {

JdbcUtils.rollbackTransaction();

}

}

}

DAO

public void daoMethod1(…) {

Connection con = JdbcUtils.getConnection();

}

public void daoMethod2(…) {

Connection con = JdbcUtils.getConnection();

}

在Service中调用了JdbcUtils.beginTransaction()方法时,JdbcUtils要做准备好一个已经调用了setAuthCommitted(false)方法的Connection对象,因为在Service中调用JdbcUtils.beginTransaction()之后,马上就会调用DAO的方法,而在DAO方法中会调用JdbcUtils.getConnection()方法。这说明JdbcUtils要在getConnection()方法中返回刚刚准备好的,已经设置了手动提交的Connection对象。

在JdbcUtils中创建一个Connection con属性,当它为null时,说明没有事务!当它不为null时,表示开启了事务。

l 在没有开启事务时,可以调用“开启事务”方法;

l 在开启事务后,可以调用“提交事务”和“回滚事务”方法;

l getConnection()方法会在con不为null时返回con,再con为null时,从连接池中返回连接。

beginTransaction()

判断con是否为null,如果不为null,就抛出异常!

如果con为null,那么从连接池中获取一个Connection对象,赋值给con!然后设置它为“手动提交”。

getConnection()

判断con是否为null,如果为null说明没有事务,那么从连接池获取一个连接返回;

如果不为null,说明已经开始了事务,那么返回con属性返回。这说明在con不为null时,无论调用多少次getConnection()方法,返回的都是同个Connection对象。

commitTransaction()

判断con是否为null,如果为null,说明没有开启事务就提交事务,那么抛出异常;

如果con不为null,那么调用con的commit()方法来提交事务;

调用con.close()方法关闭连接;

con = null,这表示事务已经结束!

rollbackTransaction()

判断con是否为null,如果为null,说明没有开启事务就回滚事务,那么抛出异常;

如果con不为null,那么调用con的rollback()方法来回滚事务;

调用con.close()方法关闭连接;

con = null,这表示事务已经结束!

JdbcUtils.java

public class JdbcUtils {

  private static DataSource dataSource = new ComboPooledDataSource();

  private static Connection con = null;

  public static DataSource getDataSource() {

    return dataSource;

  }

  public static Connection getConnection() throws SQLException {

    if(con == null) {

      return dataSource.getConnection();

    }

    return con;

  }

  public static void beginTranscation() throws SQLException {

    if(con != null) {

      throw new SQLException("事务已经开启,在没有结束当前事务时,不能再开启事务!");

    }

    con = dataSource.getConnection();

    con.setAutoCommit(false);

  }

  public static void commitTransaction() throws SQLException {

    if(con == null) {

      throw new SQLException("当前没有事务,所以不能提交事务!");

    }

    con.commit();

    con.close();

    con = null;

  }

  public static void rollbackTransaction() throws SQLException {

    if(con == null) {

      throw new SQLException("当前没有事务,所以不能回滚事务!");

    }

    con.rollback();

    con.close();

    con = null;

  }

}

4 再次修改JdbcUtils

现在JdbcUtils有个问题,如果有两个线程!第一个线程调用了beginTransaction()方法,另一个线程再调用beginTransaction()方法时,因为con已经不再为null,所以就会抛出异常了。

我们希望JdbcUtils可以多线程环境下被使用!这说明最好的方法是为每个线程提供一个Connection,这样每个线程都可以开启自己的事务了。

还记得ThreadLocal类么?

public class JdbcUtils {

  private static DataSource dataSource = new ComboPooledDataSource();

  private static ThreadLocal<Connection> tl = new ThreadLocal<Connection>();

  public static DataSource getDataSource() {

    return dataSource;

  }

  public static Connection getConnection() throws SQLException {

    Connection con = tl.get();

    if(con == null) {

      return dataSource.getConnection();

    }

    return con;

  }

  public static void beginTranscation() throws SQLException {

    Connection con = tl.get();

    if(con != null) {

      throw new SQLException("事务已经开启,在没有结束当前事务时,不能再开启事务!");

    }

    con = dataSource.getConnection();

    con.setAutoCommit(false);

    tl.set(con);

  }

  public static void commitTransaction() throws SQLException {

    Connection con = tl.get();

    if(con == null) {

      throw new SQLException("当前没有事务,所以不能提交事务!");

    }

    con.commit();

    con.close();

    tl.remove();

  }

  public static void rollbackTransaction() throws SQLException {

    Connection con = tl.get();

    if(con == null) {

      throw new SQLException("当前没有事务,所以不能回滚事务!");

    }

    con.rollback();

    con.close();

    tl.remove();

  }

}

5 转账示例

public class AccountDao {

  public void updateBalance(String name, double balance) throws SQLException {

    String sql = "update account set balance=balance+? where name=?";

    Connection con = JdbcUtils.getConnection();

    QueryRunner qr = new QueryRunner();

    qr.update(con, sql, balance, name);

  }

}

public class AccountService {

  private AccountDao dao = new AccountDao();

  public void transfer(String from, String to, double balance) {

    try {

      JdbcUtils.beginTranscation();

      dao.updateBalance(from, -balance);

      dao.updateBalance(to, balance);

      JdbcUtils.commitTransaction();

    } catch(Exception e) {

      try {

        JdbcUtils.rollbackTransaction();

      } catch (SQLException e1) {

        throw new RuntimeException(e);

      }

    }

  }

}

AccountService as = new AccountService();

as.transfer("zs", "ls", 100);

客户关系管理系统

1. 项目框架搭建

l 导入原型(只有页面,但没有功能的一个项目,功能都是直接跳转)!

l 功能分析:

Ø 添加客户;

Ø 查询所有客户

Ø 编辑客户:

¨ 加载这个客户到表单中显示

¨ 修改客户

Ø 删除客户(你们的)

Ø 多条件组合查询

l 创建表

l 创建包:公司名.项目名.分层,

Ø cn.itcast.cstm.domain:Customer,它与表单和t_customer表对应

Ø cn.itcast.cstm.dao:CustomerDao

Ø cn.itcast.cstm.service:CustomerService,它没有业务,其实它不存在都可以!

Ø cn.itcast.cstm.web.servlet:CustomerServlet

l 导包:

Ø mysql驱动

Ø c3p0(两个,一个配置文件)

Ø dbutils

Ø 自己的工具JdbcUtils,它在itcast-tools.jar

Ø beantuils、logging

添加客户

l add.jsp à CustomerServlet#add()à显示添加成功!

查询客户

l top.jsp(查询客户) à CustomerServlet#findAll() à list.jsp(循环显示)

编辑客户

编辑分为两步:

1. 通过cid查询

l list.jsp(编辑链接) à CustomerServlet#preEdit() à edit.jsp(把查询出的结果显示到表单中)

l edit.jsp(表单页面) à CustomerServlet#edit() à msg.jsp(显示成功信息)

删除客户

l list.jsp(删除链接)à CustomerServlet#delete() à msg.jsp

多条件组合查询

l query.jsp à CustomerServlet#query() à list.jsp

1功能内容

l 添加客户

l 修改客户

l 删除客户

l 查看客户

2 演示

添加客户

查看客户

修改客户

删除客户

3 搭建环境

1. 创建一个空项目,例如为customer

2. 导入jar包:

l itcast.jar

l mysql.jar

l dbutil.jar、logging.jar

l beanutils.jar

l c3p0.jar,…

3. 页面搭建

l index.jsp(forward到main.jsp)

l main.jsp(框架页,两帧,对应top.jsp和body.jsp)

l top.jsp(logo和两个链接:“添加客户”和“查看客户”)

l body.jsp(只有欢迎信息)

l add.jsp(添加客户表单)

l mod.jsp(修改客户表单)

l del.jsp(删除客户表)

4. 处理页面跳转问题

编写CustomerServlet,处理页面跳转问题!

4 创建表和类

customer

customer

字段

类型

说明

cid

char(32)

主键

cname

varchar(30)

客户姓名

gender

varchar(6)

客户性别

birthday

date

客户生日

cellphone

varchar(20)

客户手机

email

varchar(30)

客户邮箱

description

varchar(200)

客户描述

CREATE TABLE customer(

cid CHAR(32) PRIMARY KEY,

cname VARCHAR(30) NOT NULL,

gender VARCHAR(6) NOT NULL,

birthday DATE,

cellphone VARCHAR(20) NOT NULL,

email VARCHAR(30),

description VARCHAR(200)

);

  Customer类这里就省略了!

5 添加客户分析

1. 当用户点击“添加客户”链接时,通过CustomerServlet的addPre转发到add.jsp

2. 在add.jsp中提交表单时,由CustomerServlet来处理请求:

l 获取表单数据,封装到Customer对象中;

l 调用CustomerService代码,把Customer添加到数据库;

l 向页面输出“添加成功”。

6 查看客户分析

1. 当用户点击“查看客户”链接时,通过CustomerServlet的list方法来处理:

l 通过CustomerService获取所有客户信息;

l 保存到request中;

l 转发到list.jsp

l list.jsp使用<c:forEach>查看信息

7 修改客户

1. 当用户在list.jsp页面中点击“修改”时,通过CustomerServlet的modPre方法来处理:

l 获取cid,即要修改的客户的cid;

l 通过cid来获取Customer对象

l 把Customer对象保存到request中

l 转发到mod.jsp

2. 当用户在mod.jsp提交表单时,通过Customer的mod方法来处理:

l 获取表单数据,封装到Customer对象中;

l 调用CustomerService的方法完成修改;

l 向页面输出“修改成功”。

8 删除客户

1. 当用户在list.jsp页面中点击“删除”时,通过CustomerServlet的delPre方法来处理:

l 获取cid;

l 通过cid获取Customer对象;

l 把Customer对象保存到request中

l 转发到del.jsp

2. 当用户在del.jsp页面点击删除时,通过Customer的del广场来处理:

1. 获取cid

2. 通过CustomerService来完成删除

3. 向页面输出“删除成功”

分页

1 分页数据分析

页面需要什么数据:

l 当前页页码(currPageCode):Servlet提供;

l 共几页(totalPage):Servlet提供;

l 当前页数据(datas):Servlet提供;

Servlet需要什么数据:

l 当前页页码(currPageCode):页面提供,如果页面没有提供,那么默认为1;

l 总记录数(totalRecord):通过数据库来查询;

l 每页记录数(pagesize):系统数据;

l 共几页(totalPage):通过totalRecord和pagesize来计算;

l 当前页第一行记录位置(currPageBeginIndex):通过currPageCode和pagesize计算;

l 当前页数据(datas):通过currPageBginIndex和pagesize查询数据库;

2 PageBean

把分布数据封装成PageBean类对象

public class PageBean<T> {

  private List<T> datas;// 当前页记录数, 需要传递

  private int totalRecord;// 总记录数, 需要传递

  private int currPageCode;// 当前页码, 需要传递

  private int pagesize;// 每页记录数, 需要传递

  private int totalPage;// 总页数, 计算

  private int currPageBeginIndex; //需要计算

  public PageBean(int currPageCode, int totalRecord, int pagesize) {

    this.currPageCode = currPageCode;

    this.totalRecord = totalRecord;

    this.pagesize = pagesize;

    init();

  }

  private void init() {

    this.totalPage = totalRecord / pagesize;

    if(totalRecord % pagesize != 0) {

      this.totalPage++;

    }

    this.currPageBeginIndex = (this.currPageCode-1) * this.pagesize;

  }

...

}

3 分页分析

4 页码列表

  

其中红框中的就是页码列表!

4.1 页面需要的数据:

l 列表的开始页码(beginIndex);

l 列表的结束页码(endIndex)。

例如开始页码为11,结束页码为18,那么就显示:

对于页面,它只需要beginIndex和endIndex,然后使用<c:forEach>就可以循环显示了!然后再判断一下遍历的数字如果与pageBean.currPageCode相等,那么就不要显示为链接即可。

4.2 PageBean

页面需要的数据由PageBean来提供,即为pageBean添加两个方法:

l int getBeginIndex()

l int getEndIndex()

但是,PageBean想计算这两个值,也要需要两个系统数据:

l pageCodeListSize:页码列表长度,下图中的列表长度为8,即最多显示8个页码;

l currPageCodeListIndex:当前页码在列表中的位置,下图中当前页码的位置为4,位置是从1开始计算。

4.3 计算beginIndex

计算beginIndex分为4步:

1. 如果总页数(totalPage)小于列表长度(pageCodeListSize),那么beginIndex为1;

例如,当前数据一共5页,那么列表的开始页码一定是1。

2. 当上面条件不成立时,使用当前页码在列表中的位置(currPageCodeListIndex),以及当前页码(currPageCode)来推算出beginIndex。

上图中当前页码(currPageCode)为14,当前页码位置为4(currPageCodeListIndex),推算出beginIndex为11,即beginIndex = currPageCode- currPageCodeListIndex+1;

3. 第2步计算出的beginIndex如果小于1,即让beginIndex=1。

第2步的计算可能会出现问题,例如在当前页码(currPageCode)为1时,那么上面的计算就会出现beginIndex小于1的情况!这种beginIndex小于1的情况,只有在currPageCode为1、2、3时会出现。你可以去套用一下第2步的公式,会发现这个问题的!

也就是说,beginIndex的最小值就是1

4. 第2步计算出的beginIndex可能会导致列表长度不正确

当cuarrPageCode为30时,那么通过第2步计算出的beginIndex为27,但是如果totalPage为30呢?因为一共就30页,总不能显示31出来吧。那么显示:27、28、29、30,这就只有4个页码,但列表长度应该为8,所以出错。

我们为了验证第2步是否出现这个错误,需要通过得到的beginIndex来推算endIndex。例如currPageCode为30,计算出beginIndex为27,再通过beginIndex计算出endIndex为34。然后查看endIndex是否大于totalPage,如果大于了totalPage,那么应该使用totalPage减去pageCodeListSize再加1,得到正确的beginIndex。

int endIndex = beginIndex+pageCodeListSize-1;

if(endIndex > totalPage) beginIndex = totalPage-pageCodeListSize + 1;

  public int getBeginIndex() {

    if(totalPage <= pageCodeListSize) return 1;

    int begin = currPageCode – currPageCodeListIndex+1;

    if(begin < 1) begin = 1;

    int end = begin + pageCodeListSize-1;

    if(end > totalPage) begin = totalPage – pageCodeListSize+1;

    return begin;

  }

4.4 计算endIndex

计算endIndex也是分为4步

1. 如果totalPage小于pageCodeListSize,那么endIndex为totalPage

2. 通过pageCodeListSize、currPageCode、currPageCodeListIndex来推出endIndex。

currPageCode=14,pageCodeListSize=8,currPageCodeListIndex=4,所以

endIndex = currPageCode+(pageCodeListSize=currPagecodeListIndex)=18。

3. 第2步的计算结果可能会大于totalPage,那么就重置endIndex为totalPage

如果currPageCode为30,那么通过第2步计算出的endIndex为34,但是如果totalPage为30呢?那么就让endIndex=totalPage。

当currPageCode为27、28、29、30时,如果totalPage=30,那么第2步都会计算出错。这时就把endIndex=totalPage

4. 第2步的计算结果可能会导致错误的列表长度

当currPageCode为1时,那么通过第2步计算的endIndex为5,即列表为1、2、3、4、5,但列表长度应该为8,所以当第2步计算出的endIndex< pageCodeListSize,那么让endIndex等于列表长度。

  public int getEndIndex() {

    if(totalPage <= pageCodeListSize) return totalPage;

    int end = currPageCode + (pageCodeListSize-currPageCodeListIndex);

    if(end > totalPage) end = totalPage;

    if(end < pageCodeListSize) end = pageCodeListSize;

    return end;

  }

超全面的JavaWeb笔记day19<Service>的更多相关文章

  1. 超全面的JavaWeb笔记day03<JS对象&函数>

    1.js的String对象(****) 2.js的Array对象 (****) 3.js的Date对象 (****) 获取当前的月 0-11,想要得到准确的月 +1 获取星期时候,星期日是 0 4.j ...

  2. 超全面的JavaWeb笔记day13<JSTL&自定义标签>

    1.JSTL标签库(重点) core out set remove url if choose when otherwise forEach fmt formatDate formatNumber 2 ...

  3. 超全面的JavaWeb笔记day10<Response&Request&路径&编码>

    1.Response 2.Request 3.路径 4.编码 请求响应流程图 response 1.response概述 response是Servlet.service方法的一个参数,类型为java ...

  4. 超全面的JavaWeb笔记day17<JDBC>

    1.JDBC的原理 是由JavaEE提供的连接数据库的规范 需要由各大数据库的厂商提供对JDBC的实现类 2.四大核心类 3.四大参数 driverClassName url username pas ...

  5. 超全面的JavaWeb笔记day15<mysql数据库>

    1.数据库的概述 2.SQL 3.DDL 4.DML 5.DCL 6.DQL MySQL 数据库 1 数据库概念(了解) 1.1 什么是数据库 数据库就是用来存储和管理数据的仓库! 数据库存储数据的优 ...

  6. 超全面的JavaWeb笔记day12<Jsp&JavaBean&El表达式>

    1.JSP三大指令 page include taglib 2.9个内置对象 out page pageContext request response session application exc ...

  7. 超全面的JavaWeb笔记day11<JSP&Session&Cookie&HttpSession>

    1.JSP 2.回话跟踪技术 3.Cookie 4.HttpSession JSP入门 1 JSP概述 1.1 什么是JSP JSP(Java Server Pages)是JavaWeb服务器端的动态 ...

  8. 超全面的JavaWeb笔记day09<Servlet&GenericServlet&HttpServlet&ServletContext>

    1.Servlet概述 2.Servlet接口 3.GenericServlet 4.HttpServlet 5.Servlet细节 6.ServletContext(重要) Servlet概述 生命 ...

  9. 超全面的JavaWeb笔记day08<Tomcat&Web应用&HTTP协议>

    1.常用软件体系结构 BS:浏览器/服务器 CS:客户端/服务器 WEB资源 动态资源 JSP Servlet 静态资源 html 常用服务器 Tomcat Weblogic Resin JBOSS ...

随机推荐

  1. LeetCode: ZigZag Conversion 解题报告

    ZigZag ConversionThe string "PAYPALISHIRING" is written in a zigzag pattern on a given num ...

  2. ios UITextField文本框基本使用,以及所有代理方法的作用

    /* UITextField文本输入框 */ UITextField * textField = [[UITextField alloc]initWithFrame:CGRectMake(50, 50 ...

  3. 如何在Windows Server 2008 R2下搭建FTP服务

    在Windows Server 2008 R2下搭建FTP服务,供客户端读取和上传文件 百度经验:jingyan.baidu.com 工具/原料 Windows Server 2008 R2 百度经验 ...

  4. 在Linux下制作一个磁盘文件,在u-boot 阶段对emmc 烧写整个Linux系统方法

    在Linux 下制作一个磁盘文件, 可以给他分区,以及存储文件,然后dd 到SD卡便可启动系统. 在u-boot 下启动后可以读取该文件,直接在u-boot 阶段就可以做烧写操作,省略了进入系统后才进 ...

  5. JAVA里使用CKEditor和CKFinder的配置

    在JSP里使用CKEditor和CKFinder的配置 CKEditor的编辑器工具栏中有一项“图片域”,该工具可以贴上图片地址来在文本编辑器中加入图片,但是没有图片上传. “预览”中有一大堆鸟语,看 ...

  6. 对于REST中无状态(stateless)的一点认识(转)

    在请求中传递SessionID被普遍认为是unRESTful的,而将用户的credentials包含在每个请求里又是一种非常RESTful的做法.这样一个问题经常会造成困扰.本文就REST的一些概念进 ...

  7. 【Mac双系统设置系统默认启动系统】解决方式

    解决方式1: 开机时长按option键,进入系统选择界面: 用左右方向键选择到你要设置为默认启动的盘, 然后同一时候按下ctrl+enter键.就可以将其设置为默认启动的系统. 解决方式2: 选择ma ...

  8. 二、Linux 静态IP,动态IP配置

    Linux 静态IP,动态IP配置 第一步:激活网卡 系统装好后默认的网卡是eth0,用下面的命令将这块网卡激活. # ifconfig eth0 up 第二步:设置网卡进入系统时启动 想要每次开机就 ...

  9. 又一个错误" Fatal error: Call to undefined function myabp_print_screenshot_all() "

    xxx ( ! ) Fatal error: Call to undefined function myabp_print_screenshot_all() in D:\wamp\www\wp-con ...

  10. Connector/c++ 查询Mysql,出现 can't fetch because not on result set 错误

    使用 Connector/C++ 查询 Mysql 时,出现错误,提示如下: can't fetch because not on result set, 出现原因可以看这里:http://stack ...