web学习七——JDBC

  1. JDBC

9.1 简介

Sun公司提供一套 用于 专门使用java语言去 操作 关系型数据的一套api 结构规范.
这里写图片描述

9.2 JDBC相关的类

Jdbc 中 所用到的类 都是 在jdk 中已经集成 进来的.
这里写图片描述

9.3 JDBC 快速入门

//首先导入具体的驱动的jar包
			//1.注册驱动-----告诉要操作的是哪个数据库
			DriverManager.registerDriver(new Driver());
			
			//2.建立与数据库的连接---connection
			conn = DriverManager.getConnection("jdbc:mysql://localhost/day14","root","root");
			
			//3.获得可以发送sql语句的statement			
			stmt = conn.createStatement();
			
			//4.执行sql语句,拿到结果集对象
			//rs就封装了这个结果集对象			
			rs = stmt.executeQuery("select * from users");
			
			//5.解析结果集对象
			while(rs.next()) {
				int id = rs.getInt("id");
				String username = rs.getString("username");
				String password = rs.getString("password");
				String nickname = rs.getString("nickname");					System.out.println("id:"+id+"username:"+username+"password:"+password+"nickname"+nickname);
			}
			
			//6.释放资源---由于数据库连接非常稀缺,所以在操作完成后,记得释放资源,都会放到finally代码块中去
				rs.close();
				stmt.close();
				conn.close();

9.4 注意点

1. 注册驱动

DriverManager.registerDriver(new Driver());

这种方式不好, 实际 开发中 不采用这种方式 . Why? 
这里写图片描述

采取如下这种 迂回的方式, 很巧

//这里本来是加载字节码,但是加载字节码的时候又会执行Driver类的静态代码块,而静态代码又完成了注册驱动
				Class.forName("com.mysql.jdbc.Driver");

两个好处:
好处一:
避免注册两次驱动
好处二:
加载字节码写的是 字符串 ,那么就可以将 字符串 挪到配置文件中去, 使用 程序去读取配置文件的信息, 将来要 其他的数据库时, 就不用更改源代码了, 只需要去修改配置文件即可, 这样程序 通用性就大大提高了.

2. 获得链接
获得链接时, 需要 url, user, password , 这样的三个参数 是必须要的

//2.建立与数据库的连接---connection,可以简写为:jdbc:mysql:///day14
			conn = DriverManager.getConnection("jdbc:mysql://localhost/day14","root","root");

url
这里写图片描述
Connection对象
Connection conn = DriverManager.getConnection("jdbc:mysql://localhost/day14","root","root");
建立链接返回的是Connectioin对象,这个对象就表示与数据库服务器 建立的连接 , 后续的所有的操作都是基于这个对象的 。
这里写图片描述

3. 获得发送sql语句的statement对象
Statement stmt = conn.createStatement();
这里 的statement 就可以发送标准 sql语句命令给数据库的对象, 通过它, 我们可以去执行任意类型的sql 语句。
查询:rs = stmt.executeQuery("select * from users");
增删改:stmt.executeUpdate(sql);
其它sql语句:stmt.execute(sql);
批处理:

			//将sql语句加入批处理中...
			stmt.addBatch(sql);
			//执行批处理
			stmt.executeBatch();

4. 结果集ResultSet
只会在 做查询的时候有. 你做增删改的时候是不会有的
拿到了 结果集resulstSet对象, 就需要去查询响应的api 去获得 封装的数据了, 就应该去找
getXxx的方法。

//5.解析结果集对象
			while(rs.next()) {
				int id = rs.getInt("id");
				String username = rs.getString("username");
				String password = rs.getString("password");
				String nickname = rs.getString("nickname");
				
				System.out.println("id:"+id+"username:"+username+"password:"+password+"nickname"+nickname);
			}

结果集的原理分析
这里写图片描述

5. 释放资源
数据库链接非常的稀缺, 所以在不用链接的时候要将相应的资源给释放掉。
所有资源都是基于 Connection对象去获得的

Connection conn = DriverManger.getConnection();
Statement stmt = conn. createStatement()
ResultSet rs = Stmt.executeQuery();

一般关闭资源的代码是放在finally 代码块 中的. (一般情况下, 建议是按照获得资源的相反的顺序去关闭)你先获得的, 后关闭.

if(rs!=null) {
				try {
					rs.close();
				} catch (SQLException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
				rs=null;
			}
			
			if(stmt!=null) {
				try {
					stmt.close();
				} catch (SQLException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
				stmt=null;
			}
			
			if(conn!=null) {
				try {
					conn.close();
				} catch (SQLException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
				conn=null;
			}

6. Sql注入
Sql注入指的是, 在给sql语句传递参数的过程中,通过传递sql语句的关键字, 从而更改原有的sql语句的含义,从而达到 不可告人的秘密的目的.
这里写图片描述
本质问题是 拼接了sql语句关键字, 从而更改了原有的含义.
解决:
在传入的sql语句时候, 就使用PreaparedStatement进行预编译处理, 预编译之后,再次传入 参数时, 如果传递的进来有sql语句的关键字, 那么也不会当作 关键字去处理了, 从而 就解决了问题.

PreparedStatement stmt = conn.prepareStatement("insert into mynovel values(null,?)");
//替换占位符
		File file=new File("src/novel.txt");
		Reader reader=new FileReader(file);
		stmt.setCharacterStream(1, reader,file.length());
		
		int count=stmt.executeUpdate();

7. 读取大数据
PreparedStatement.setCharacterStream(index, reader, length);
//注意length长度须设置,并且设置为int型
//当包过大时修改配置:[mysqld] max_allowed_packet=64M

public class BigDataTest {
	//存文本数据到数据库中去
	//存小说
	@Test
	public void test1() throws Exception {
		Connection conn = JdbcUtils.getConnection();
		PreparedStatement stmt = conn.prepareStatement("insert into mynovel values(null,?)");	
		//替换占位符
		File file=new File("src/novel.txt");
		Reader reader=new FileReader(file);
		stmt.setCharacterStream(1, reader,file.length());
		
		int count=stmt.executeUpdate();
		JdbcUtils.release(null, stmt, conn);
	}
	//取出数据库中的文本数据
	@Test
	public void test2() {
		Connection conn = JdbcUtils.getConnection();
		try {
			PreparedStatement stmt = conn.prepareStatement("select * from mynovel");
			ResultSet rs=stmt.executeQuery();
			
			if(rs.next()) {
				
				rs.getInt("id");
//reader = resultSet. getCharacterStream(i);
//等价于
//reader = resultSet.getClob(i).getCharacterStream();
				Reader reader = rs.getCharacterStream("noveltext");
				FileWriter writer=new FileWriter("src/backup.txt");
				//byte[] buff=new byte[1024];
				int len=0;
				while((len=reader.read())>0) {
					writer.write(len);
				}
				reader.close();
				writer.close();
			}
			JdbcUtils.release(rs, stmt, conn);
		} catch (Exception e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}		
	}
}

8. javaee三层结构及dao模式介绍
这里写图片描述

9.5 事务

9.5.1 ACID
这里写图片描述
9.5.2 隔离性
在数据库的事务特性中, 最重要的是隔离性,如果 多个事务共同同时操作 同一个表中数据时, 不考虑 隔离性,那么就会引发灰常严重的问题, 主要会有以下的三类问题:
第一类: 脏读(dirty read ), 一个事务在操作的时候,读到了其他的事务未提交的数据, 这叫做脏读,也是最严重的问题.
第二类: 不可重复读(unreatable read), 一个事务在操作的时候,读到的其他的事务已经提交的数据 , 主要指通过update 语句对数据库中现有的数据进行了更新操作
第三类: 虚读,幻读(phantom read), 一个事务在操作的时候,读到的其他的事务已经提交的数据, 主要值 使用Insert 语句 插入了新的记录
针对以上三类问题,数据库 提供了4 种 隔离级别, 用于解决以上三类问题.
第一种: read uncommitted (读未 提交), 会产生 脏读, 不可重复读, 虚读的发生是有概率的
第二种: read committed(读已提交), 不会产生脏读, 但是会引发不可重复读. 虚读的发生是有概率的
第三种: repeatable read (可以重复读), 不会发生脏读, 不可重复读, 虚读发生是有概率的
第四种: serializable (串行化), 不会发生以上的三类问题.
对于 以上四种隔离级别,串行化解决了所有的问题, 但是效率是最低的, 实际开发中,最常用的是
第二种以及第三种隔离级别.
Mysql: 默认的隔离级别是 repeatable read
Oracle: 默认的隔离级别是 read committed
通过如下的命令 可以去更改 隔离级别 :
set session transaction isolation level 隔离级别值---- 设置当前的隔离级别了
select @@tx_isolation ----查询当前的隔离级别
这些问题, 都是在 事务操作过程中 才会出现的. 所以 肯定会去使用
Start transaction ----开启事务
Rollback----- 回滚事务
Commit —提交事务
实际开发过程中,是通过 java代码去实现 事务的管理控制,设置隔离级别
9.5.3 java代码去实现 事务的管理控制
如何去设置隔离级别呢?
找到Connection类
有如下的api 去设置 隔离级别
这里写图片描述
针对 数据库中的隔离级别,sun公司在定义jdbc的接口规范的时候,
就设置了4 个常量, 用于 设置具体的隔离级别 .
这里写图片描述
9.5.4 注意
1.在管理事务的时候,使用的是Connection对象
conn.setAutoCommit(false);//开启事务
conn.commit();//提交事务
conn.rollback();//回滚事务,撤销操作
管理事务的时候,为了确保出现异常可以回滚事务,撤销操作或提交操作,那么必须要确保始终操作的是同一个Connection对象
2.在操作事务的时候,可能会发生异常,所以一般将异常最大化,也就是说,通常是以下形式:
catch (Exception e) {
//回滚
}
3.设置回滚点,是为了提高效率,需要注意的是,设置回滚点的时候,同样你要鉴别在哪些地方设置回滚点合适(合理的地方)

9.6 连接池技术

连接池的核心思想是:一次性的批量的弄出多个连接对象放到连接池中,当需要操作数据库的时候就
从连接池中取出一个链接使用,当用完了连接之后再次将连接放回到池子中去,这样就避免了不断的创建连接,不断的释放连接的过程,从而一定程序上显著的提高程序的性能.
这里写图片描述
9.6.1 c3p0数据库连接池的使用
c3p0-config.xml

<c3p0-config>
  <default-config>
    <property name="driverClass">com.mysql.jdbc.Driver</property>
    <property name="jdbcUrl">jdbc:mysql://localhost/day16</property>
    <property name="user">root</property>
    <property name="password">root</property>
  </default-config>

  <!-- This app is massive! -->
  <named-config name="intergalactoApp"> 
    <property name="maxPoolSize">1000</property>
	 <property name="maxStatements">0</property> 
    <property name="maxStatementsPerConnection">5</property>
  </named-config>
</c3p0-config>
/*
 * c3p0数据库连接池的使用:
 * c3p0是一个开源的数据库连接池
 * 找相关的文档   jar包      api   quickstart
 * 首先导入jar包      c3p0-0.9.1.2.jar
 * 编写程序 核心类  ComboPooledDataSource 
 */
 public class c3p0Test {
	@Test
	public void test() {
		//就会去classpath路径下找c3p0-config.xml文件,然后将获得连接所需要的参数信息设置进来
		Connection conn=null;
		PreparedStatement stmt=null;
		try {
			ComboPooledDataSource cpds = new ComboPooledDataSource();			
			conn = cpds.getConnection();
			stmt = conn.prepareStatement("update account set money=? where name=?");
			stmt.setDouble(1, 00);
			stmt.setString(2, "ccc");			
			stmt.executeUpdate();			
			System.out.println(conn);
		} catch (Exception e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}finally {
			JdbcUtils.release(null, stmt, conn);
		}		
	}
	@Test
	public void test1() {
		Connection conn=null;
		PreparedStatement stmt=null;		
		try {
			ComboPooledDataSource cpds = new ComboPooledDataSource();
			cpds.setDriverClass( "com.mysql.jdbc.Driver" ); //loads the jdbc driver            
			cpds.setJdbcUrl( "jdbc:mysql://localhost/day16" );
			cpds.setUser("root");                                  
			cpds.setPassword("root");			
			conn = cpds.getConnection();			
			stmt = conn.prepareStatement("update account set money=? where name=?");
			stmt.setDouble(1, 200);
			stmt.setString(2, "ccc");			
			stmt.executeUpdate();
		} catch (Exception e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}finally {
			//连接池都会实现对close方法加强,确保在调用了close的时候,将connection放回到连接池
			JdbcUtils.release(null, stmt, conn);
		}
	}}

9.6.2 自定义连接池的实现(day15/src/com.lln.transaction/MyDataSource)
9.6.3 java中对类的方法加强

  • 1.继承----匿名内部类
  • 2.装饰设计模式
  • 3.动态代理
public class MethodEhancement {
	//匿名内部类,需要去new对象(知道构造函数)
	@Test
	public void test() {		
		Cat cat=new Cat() {
		public void run() {
			//你想干的事.....
			System.out.println("小黑的速度达到了每秒25米....");
			super.run();
		}		
	};	
	cat.run();
		}	
	//2.装饰设计模式:
	//用的时候要满足的条件:1.装饰者和被装饰者必须要实现同样的接口,或者有共同的父类
	//2。被装饰者要传递给装饰者----装饰者持有对被装饰者的引用
	@Test
	public void test2() {
		DecratorCat cat=new DecratorCat(new Cat());
		cat.run();
	}	
	//3.动态代理
	//纯面向对象,并且api体系非常全面,你想干什么事都提供好了实现类    只要找对象就可以了
	//proxy
	//static Object newProxyInstance(ClassLoder loder,Class<?>[] interface,InvocationHander h)
	@Test
	public void test3() {
		ICat cat=new Cat();		
		ICat proxyICat = (ICat) Proxy.newProxyInstance(MethodEhancement.class.getClassLoader(), cat.getClass().getInterfaces(), new InvocationHandler() {
			@Override
		    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
				if(method.getName().equals("run")) {
					//说明调用的是run方法,加强一下
					System.out.println("小黑是一个战斗机");
					return method.invoke(cat, args);
				}
		        return method.invoke(cat, args);
		    }
		});		
//		proxyICat.run();		
		proxyICat.hashCode();
	}
}
class DecratorCat implements ICat{	
	private ICat cat;
	public DecratorCat(ICat cat) {
		this.cat=cat;
	}
	@Override
	public void run() {
		//同时可以添加自己的逻辑
		System.out.println("小黑抓住老鼠只用了3秒时间.....");
		//调用原有的逻辑,
		cat.run();
	}	
}
//被装饰者
 class Cat implements ICat{	
	public void run() {
		System.out.println("小黑在黑暗中抓住了一只老鼠....");
	}
} 
 interface ICat{
	 public void run() ;
 }

9.7 获取元数据

public class DataMetaDemo {
/*
 * 获得数据库的元数据信息
 * 1.DatabaseMetadata
 * 2.ParameterMetadata----获得占位符个数
 * 3.ResultSetMetadata
 * 
 * 环境:导入mysql的驱动jar包,c3p0连接池
 */
	@Test
	public void testResultSetMetadata() {
		Connection conn = JdbcUtils.getConnection();
		try {
			PreparedStatement stmt = conn.prepareStatement("select * from account");
			
			ResultSet rs = stmt.executeQuery();
			//这个对象封装了查询的结果二维表结构有多少列,每一列具体的名称,每一列具体的类型
			ResultSetMetaData rsmd = rs.getMetaData();
		    //列的数量
			int columnCount = rsmd.getColumnCount();
			for(int i=0;i<columnCount;i++) {
				String columnName = rsmd.getColumnName(i+1);
				int columnType = rsmd.getColumnType(i+1);
				String columnTypeName = rsmd.getColumnTypeName(i+1);
				
				System.out.println("columnName: "+columnName);
				System.out.println("columnType: "+columnType);
				System.out.println("columnTypeName: "+columnTypeName);
			}
			
			
		} catch (Exception e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		
	}
	@Test
	public void testParameterMetadata() {
		Connection conn = JdbcUtils.getConnection();
		try {
			PreparedStatement stmt = conn.prepareStatement("???????");
			
			ParameterMetaData pmd = stmt.getParameterMetaData();
			int count = pmd.getParameterCount();
			//替换占位符
			for(int i=0;i<count;i++)
			{
			//stmt.setXXX	
			}
			
		} catch (Exception e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		
	}
	@Test
	public void testDatabaseMetadata() {
		Connection conn = JdbcUtils.getConnection();
		try {
			DatabaseMetaData metaData = conn.getMetaData();
			String userName = metaData.getUserName();
			String url = metaData.getURL();
			
			String databaseProductName = metaData.getDatabaseProductName();
			String databaseProductVersion = metaData.getDatabaseProductVersion();
			
			String driverName = metaData.getDriverName();
			String driverVersion = metaData.getDriverVersion();
			boolean readOnly = metaData.isReadOnly();
			
			System.out.println("url: "+url);
			System.out.println("userName: "+userName);
			System.out.println("databaseProductName: "+databaseProductName);
			System.out.println("databaseProductVersion: "+databaseProductVersion);
			System.out.println("driverName: "+driverName);
			System.out.println("driverVersion: "+driverVersion);
			System.out.println("readOnly: "+readOnly);
			
		} catch (Exception e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}
	
}

9.8 DbUtils框架

使用:

QueryRunner runner=new QueryRunner(JdbcUtils.getDataSource());

runner.update("insert into account values(?,?,?)", new Object[] {4,"颖宝",40000000});

//BeanListHandler:把结果集中的数据转成JavaBean实体
List<Account> list = runner.query("select * from account", new BeanListHandler<Account>(Account.class));		
		for(Account account:list) {
			System.out.println(account);
		}

//ArrayHandler:把结果集中的第一行数据转成对象数组
Object[] query = runner.query("select * from account", new ArrayHandler());
System.out.println(Arrays.toString(query));

//ArrayListHandler:把结果集中的每一行数据转成一个数组,再放到List中去
List<Object[]> list = runner.query("select * from account", new ArrayListHandler());
			for(Object[] array:list) {
				System.out.println(Arrays.toString(array));
			}

//ColumnListHandler:把结果集中的某一列数据存到List中去
List<Object> list = runner.query("select * from account", new ColumnListHandler());
						System.out.println(list);

//KeyedHandler:把结果集中的每一行数据都都封装到一个Map<列名,列值>里去,再把这些map再存到一个map里去,其key为指定的key						
Map<Object, Map<String, Object>> mapinmap = runner.query("select * from account", new KeyedHandler("id"));
					Set<Entry<Object, Map<String, Object>>> entrySet = mapinmap.entrySet();
					for(Entry<Object, Map<String, Object>> entry:entrySet) {
						Object key=entry.getKey();
						Map<String, Object> value = entry.getValue();
						System.out.println(key+":"+value);
					}

//MapHandler:把结果集中的第一行数据都封装到一个Map里,key是列名,value就是对应的值
Map<String, Object> map = runner.query("select * from account", new MapHandler());
					Set<String> keySet = map.keySet();
					for(String key:keySet) {
						Object value = map.get(key);
						System.out.println(key+":"+value);
					}

//MapListHandler:把结果集中的第一行数据都封装到一个Map里,key是列名,value就是对应的值
List<Map<String, Object>> list = runner.query("select * from account", new MapListHandler());					
					for(Map<String,Object> map:list) {
						Set<String> keySet = map.keySet();
						for(String key:keySet) {
							Object value = map.get(key);
							System.out.println(key+":"+value);
						}
						System.out.println("=================");
					}

//ScalarHandler:用于做单值查询的情况
Long count = (Long) runner.query("select count(*) from account", new ScalarHandler());
					System.out.println(count);

9.9 客户信息管理系统

9.9.1 数据库设计
1、创建新的数据库 create database customersystem;
2、创建新的用户 create user flower identified by ‘flower’;
为用户授予相应权限 grant all on customersystem.* to flower ;
3、设计数据表
create table customer(
id varchar(40) primary key,
name varchar(20),
gender varchar(10),
birthday date,
cellphone varchar(20),
email varchar(40),
preference varchar(100),
type varchar(40),
description varchar(255)
);

1、创建Connection 数据库连接
连接名称可以随意,填写密码就可以了
2、创建数据库 new database
3、创建数据表 new Table
4、创建用户授权 Manager Users

9.9.2 搭建web project环境
JavaEE 三层结构
Servlet + JSP + JavaBean + DBUtils+ DAO + MySQL
导入jar包 :JSTL 、BeanUtils、DBUtils、C3P0、mysql驱动
创建包结构
cn.itcast.customer.web 表现层
cn.itcast.customer.service 业务层
cn.itcast.customer.dao 持久层
cn.itcast.customer.utils 工具包
cn.itcast.customer.domain 实体类
编写公共程序 domain utils
Customer 类 实体类
JDBCUtils 工具类
9.9.3 功能开发
软件设计 :数据库建模设计 powerdesigner 、软件功能设计 UML图 Rational Rose ; Visio ; StarUML ; JUDE
UML常用设计图:类图、用例图、时序图、活动图
**1、**客户添加
异常处理原则:可以自己解决catch, 不可以解决throws ----- 如果选择throws 非常明确知道上层业务解决 catch异常,转换runtime异常抛出
add.jsp ----- AddCustomerServlet — CustomerService ---- CustomerDAO
解决checkbox丢失数据.

		String[] parameterValues = request.getParameterValues("hobbies");
		String values = Arrays.toString(parameterValues);		
		String hobbies = (String) values.subSequence(1, values.indexOf("]"));

存在问题:刷新提交问题
解决 :
客户端方案:在点击submit按钮一次后,禁用该按钮
服务器端方案(比较可靠) :令牌机制 ,在访问JSP页面时,生成令牌号,将令牌号保存在Session中 ,同时将令牌号放入form的隐藏字段
在提交form 后,令牌号也会提交,在服务器端用请求中令牌号与Session中令牌号 比较,如果一致,说明请求有效,使用Session令牌号后,删除
如果客户端提交令牌号 与 服务器端令牌号 不一致,说明非法请求(重复提交 )
引入 jquery的日历控件

<script type="text/javascript" src="jquery-1.7.1.min.js"></script>
<script type="text/javascript" src="jquery-ui-1.8.18.custom.min.js"></script>
<link href="jquery-ui-1.8.18.custom.css" rel="stylesheet" type="text/css"/>
<script type="text/javascript">
	$(function(){
		$("#birthday").datepicker({
			minDate: new Date(1980, 0, 01),
			maxDate: new Date(2020, 11, 31),
			yearRange :'-40:+30',
			changeMonth: true,
			changeYear: true,
			dateFormat: 'yy-mm-dd'
		});
	});
</script>

同时, 还需要 为 input输入项 生日 添加 一个 id=”birthday”
这里写图片描述
**2、**客户查询
index.jsp ----- FindAllCustomersServlet ------ CustomerService — CustomerDAO ---- list.jsp
这里写图片描述
**3、**客户信息的删除
list.jsp

<script type="text/javascript">
function confimDel(id) {
var isconfirmed=window.confirm("亲,您确定要删除吗?不能反悔了");
if(isconfirmed){
	//如果进来,就让页面进行跳转---发起请求给deleteone
	location.href="${pageContext.request.contextPath}/deleteone?id="+id;
}
}
//点击是否勾选全部的js代码
function checkAllStatus(){
	//拿到最上面的checkbox
	var checkBtn=document.getElementById("checkBtn")
	//alert(checkBtn.checked);
	var ids=document.getElementsByName("ids");
	//alert(ids.length);
	for(var i=0;i<ids.length;i++){
		ids[i].checked=checkBtn.checked;
	}
	/*
	if(checkBtn.checked){
		//如果进来,表明 勾选了,就需要把全部的checkbox都选中
		//拿到其他所有的checkbox
		var ids=document.getElementsByName("ids");
		//alert(ids.length);
		for(var i=0;i<ids.length;i++){
			ids[i].checked=true;
		}
	}else{
		//如果进来,表明没有 勾选了,就需要把其他的全部的checkbox都不选中
		//拿到其他所有的checkbox
		var ids=document.getElementsByName("ids");
		//alert(ids.length);
		for(var i=0;i<ids.length;i++){
			ids[i].checked=false;
		}
	}
	*/
}
</script>

<form action="${pageContext.request.contextPath}/deletebatch" method="post">
<table border="1" align="center" width="100%">
<tr>
<th>设置状态<input type="checkbox" name="checkBtn" id="checkBtn" onclick="checkAllStatus();"></th>
<th>客户姓名</th>
<th>客户性别</th>
<th>客户生日</th>
<th>客户邮箱</th>
<th>客户手机</th>
<th>客户爱好</th>
<th>客户类型</th>
<th>客户描述</th>
<th>客户操作</th>
</tr>

<c:forEach items="${customers}" var="customer">
<tr>
<td><input type="checkbox" name="ids" value="${customer.id}"></td>
<td>${customer.name}</td>
<td>${customer.gender}</td>
<td>${customer.birthday}</td>
<td>${customer.eamil}</td>
<td>${customer.cellphone}</td>
<td>${customer.preference}</td>
<td>${customer.type}</td>
<td>${customer.description}</td>
<!-- 一般开发过程中,在这种场景下实际是将a标签当作一个按钮去使用了,那么应该写成以下形式 -->
<td>
<a onclick="confimDel('${customer.id}')" href="javascript:void(0)">删除</a>
<a href="${pageContext.request.contextPath}/getonebyid?id=${customer.id}">更新</a>
</td>
</tr>
</c:forEach>
</table>
<div>
<input type="submit" value="删除">
</div>
</form>

单个删除
这里写图片描述
点击单个删除时, 需要 将 客户的 id 带过去
在显示客户信息的页面, 添加删除的链接 .

<a onclick="confimDel('${customer.id}')" href="javascript:void(0)">删除</a>

批量删除
批量删除的Servlet
1.获得客户的id号,注意:批量删除是多个id
2.将id传递给业务层删除,业务层调用dao层删除
3.删除之后再重新查询一次

ThreadLocal类 —用的挺多的 , android中就用到了这个类
线程本地类 内部 是维护了一个map 集合, 如果调用 set(Object )方法, 就是将 这个object 存到 这个map 中, key 就是 当前的线程, 使用threadLocal存数据时,实际上是存到 了map中, 但是存到这个map 中, key 永远都是 运行当前的代码的线程, 同样当调用get方法时, 也是以 当前的代码运行所在的线程为 key, 去 map 中 获得值 ,map.get(线程id号 )

//批量删除:要么全部删除成功,要么全部删除失败,可以放到事务中
//开启事务
TransactionUtil.startTransaction();
for(String id:ids) {
cdao.deleteOneByIdInTransacton(id);
}
//提交事务
TransactionUtil.commit();
//有异常回滚事务
TransactionUtil.rollback();
//释放资源
TransactionUtil.relase();

//把得到连接及事务有关的方法写到此类中
public class TransactionUtil {
	
	// 内部是维护了 一个 map , 这个map 的key 始终 都是 当前 的线程 
	private static ThreadLocal<Connection> tl = new ThreadLocal<Connection>();
	
	private static DataSource ds = JdbcUtils.getDataSource();
	
	public static DataSource getDataSource(){
		return ds;
	}
	
	//  这里, 获得一个 connection  对象 
	public static Connection getConnection(){
		try {
			Connection conn = tl.get();
			if(conn==null){
				//从数据连接池 中 取 一个连接 出来 
				conn = ds.getConnection();
				
				//将 取出来  connection 放到 tl中去
				tl.set(conn);
			}
			return conn;
		} catch (SQLException e) {
			throw new RuntimeException(e);
		}
	}
	
	// 开启 事务  
	// 结果 就是 返回了 一个 connection对象, 并且 将 返回的 connection放到了 threadlocal中 , 
	public static void startTransaction(){
		try {
			Connection conn = tl.get();
			if(conn==null){
				conn = getConnection();
//				tl.set(conn);
			}
			conn.setAutoCommit(false);
		} catch (SQLException e) {
			throw new RuntimeException(e);
		}
	}
	public static void rollback(){
		try {
			Connection conn = tl.get();
			if(conn==null){
				conn = getConnection();
//				tl.set(conn);
			}
			conn.rollback();
		} catch (SQLException e) {
			throw new RuntimeException(e);
		}
	}
	public static void commit(){
		try {
			Connection conn = tl.get();
			if(conn==null){
				conn = getConnection();
//				tl.set(conn);
			}
			conn.commit();
		} catch (SQLException e) {
			throw new RuntimeException(e);
		}
	}
	public static void relase(){
		try {
			Connection conn = tl.get();
			if(conn!=null){
				conn.close();
				tl.remove();
			}
		} catch (SQLException e) {
			throw new RuntimeException(e);
		}
	}
}

这里写图片描述
4. 客户信息的修改
这里写图片描述
客户信息的回显
第一类:直接通过el表达式取值
<td> <input type="text" name="name" value="${customer.name }"></td>
第二类:对于radio的回显

<td>
<!-- checked="checked" -->
 <input type="radio" name="gender" value="male" 
 <c:if test="${customer.gender=='male'}">checked="checked"</c:if>
 >男
 <input type="radio" name="gender" value="female" 
 <c:if test="${customer.gender=='female'}">checked="checked"</c:if>
 >女
 </td>
 

第三类:checkbox的回显

<td> 
<!-- checked="checked" -->
<input type="checkbox" name="preference" value="看书" 
<c:if test="${fn:contains(customer.preference,'看书')}">checked="checked"</c:if>
>看书
<input type="checkbox" name="preference" value="打球"
<c:if test="${fn:contains(customer.preference,'打球')}">checked="checked"</c:if>
>打球
<input type="checkbox" name="preference" value="玩游戏"
<c:if test="${fn:contains(customer.preference,'玩游戏')}">checked="checked"</c:if>
>玩游戏
<input type="checkbox" name="preference" value="打牌"
<c:if test="${fn:contains(customer.preference,'打牌')}">checked="checked"</c:if>
>打牌
</td>

第四类:下拉框

<td> 
<!-- selected="selected" -->
<select name="type">
<option value="普通客户"
<c:if test="${customer.type=='普通客户'}" >
selected="selected"
</c:if> 
>普通客户
</option>
<option value="VIP客户"
<c:if test="${customer.type=='VIP客户'}">
selected="selected"
</c:if> 
>VIP客户</option>
<option value="超白金客户"
<c:if test="${customer.type=='超白金客户'}" >
selected="selected"
</c:if> 
>超白金客户</option>
</select>
</td>

5. 模糊查询
根据 name, 手机号, 描述去查询.
select * from users where name like ‘%何%’;
条件查询, 需要 用户去选择按照什么条件查询, 以及查询的具体的值
在 list.jsp中添加一个表单 , 提供 条件查询入口.

<form action="${pageContext.request.contextPath}/conditionquery">
请选择查询条件:
<select name="conditionname">
<option value="name"
<c:if test="${param.conditionname=='name' }">selected="selected"</c:if>
>按姓名查询</option>
<option value="cellphone"
<c:if test="${param.conditionname=='cellphone' }">selected="selected"</c:if>
>按手机号查询</option>
<option value="description"
<c:if test="${param.conditionname=='description' }">selected="selected"</c:if>
>按描述查询</option>
</select>
<input type="text" name="conditionvalue" value="${param.conditionvalue }">
<input type="submit" value="查询">

public List<Customer> conditionQuery(String conditionname, String conditionvalue) {
		QueryRunner runner=new QueryRunner(JdbcUtils.getDataSource());
		String sql="select * from customers where "+conditionname+" like ?";
		try {
			return runner.query(sql, new BeanListHandler<Customer>(Customer.class),"%"+conditionvalue+"%");
		} catch (SQLException e) {
			throw new RuntimeException(e);
		}		
	}

6. 分页查询
这里写图片描述
分页查询的理论分为如上两种, 最终代码显示的结果是两种效果是一样的,只是原理不同.

@Test
	public void test2LogicQuery() {
		QueryRunner runner=new QueryRunner(JdbcUtils.getDataSource());
		try {
			List<Customer> list=runner.query("select * from customers", new BeanListHandler<Customer>(Customer.class));
			
			//将索引位置是11-19的数据显示出来
			//由于索引是从0开始的,所以
			List<Customer> list2 = list.subList(11, 20);
			
			for(Customer customer:list2) {
				System.out.println(customer.toString());
			}
		} catch (SQLException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}
	/*
	 * 物理分页:使用sql语句关键字,来实现查询目标段的信息
	 * 第一个?-----表示索引位置开始的位置----从0开始
	 * 第二个?-----从索引位置开始往后查询多少条显示出来
	 * select * from customers limit ?,?   10,10
	 */
	@Test
	public void test3PsychicalPageQuery() throws SQLException {
		QueryRunner runner=new QueryRunner(JdbcUtils.getDataSource());
		List<Customer> list=runner.query("select * from customers limit ?,?", new BeanListHandler<Customer>(Customer.class),10,10);
		for(Customer customer:list) {
			System.out.println(customer.toString());
		}
	}
	//不论物理分页还是逻辑分页,实现效果都是一样的,只不过是原理不同,一般两者结合使用,一般每个公司实现分页的逻辑都不同
	/*
	 * 分页必须有每页多少条数                                                                     limit ?,?
	 * 100条数据----每页显示10条----10页------显示第二页的数据-----10,10
	 * 101条数据----每页显示10条----11页------显示第三页的数据-----20,   10
	 * 110条数据----每页显示10条----11页
	 * 111条数据----每页显示10条----12页
	 * 总页数=(总条数+每页条数-1)/每页条数
	 * 开始索引的位置=(当前页数-1)*每页条数
	 */

导航条的实现
为了实现 分页时的导航条, 所以设计了 一个PageBean对象, 用于 完成 分页 的数据的封装.
涉及到的字段:每页条数, 总页数, 总记录数, 当前页, 当前页的数据
index.jsp

<a href="${pageContext.request.contextPath}/pagequery?pagenum=1">查看第一页的数据</a>

bean.jsp

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Insert title here</title>
</head>
<body>
<c:if test="${empty pageBean.customers }">
当前页没有数据
</c:if>

<c:if test="${not empty pageBean.customers }">
<h3 style="text-align: center;">当前是${pageBean.pageNum }</h3>
<table border="1" width="100%">
<tr>
<th>客户姓名</th>
<th>客户性别</th>
<th>客户生日</th>
<th>客户邮箱</th>
<th>客户手机</th>
<th>客户爱好</th>
<th>客户类型</th>
<th>客户描述</th>
</tr>

<c:forEach items="${pageBean.customers }" var="customer">
<tr>
<td>${customer.name}</td>
<td>${customer.gender}</td>
<td>${customer.birthday}</td>
<td>${customer.eamil}</td>
<td>${customer.cellphone}</td>
<td>${customer.preference}</td>
<td>${customer.type}</td>
<td>${customer.description}</td>
</tr>
</c:forEach>
</table>
<br>
<br>

<!-- 参考百度,发现如果是第一页,那么就不显示上一页 -->
<div style="text-align: center;">
<c:if test="${pageBean.pageNum!=1 }">
<a href="${pageContext.request.contextPath }/pagequery?pagenum=1">首页</a>
<a href="${pageContext.request.contextPath }/pagequery?pagenum=${pageBean.pageNum-1}">上一页</a>
</c:if> 

<!-- 参考百度,左5右4,左边显示5个数,右边显示4个数-->
<c:forEach begin="${pageBean.pageNum-5>0?pageBean.pageNum-5:1 }" end="${pageBean.pageNum+4<pageBean.totalPageNum?pageBean.pageNum+4:pageBean.totalPageNum }" var="i">
  <c:if test="${pageBean.pageNum==i }">
  <font color="red">${i}</font>
  </c:if>
  
  <c:if test="${pageBean.pageNum!=i }">
  <a href="${pageContext.request.contextPath }/pagequery?pagenum=${i}">${i}</a>
  </c:if>
  
</c:forEach>

<c:if test="${pageBean.pageNum!=pageBean.totalPageNum }">
<a href="${pageContext.request.contextPath }/pagequery?pagenum=${pageBean.pageNum+1}">下一页</a>
<a href="${pageContext.request.contextPath }/pagequery?pagenum=${pageBean.totalPageNum}">尾页</a>
</c:if> 
 </div>
</c:if>
</body>
</html>

PageBean.java

/*
 * 设计用于完成分页的JavaBean
 */
public class PageBean {

	private int numberPerPage;//每页条数
	private int pageNum;//目标要看的是哪一页
	private int totalRecordsCount;//总的记录数
	private int totalPageNum;//总页数
	private List<Customer> customers;//当前页的数据
	public int getNumberPerPage() {
		return numberPerPage;
	}
	public void setNumberPerPage(int numberPerPage) {
		this.numberPerPage = numberPerPage;
	}
	public int getPageNum() {
		return pageNum;
	}
	public void setPageNum(int pageNum) {
		this.pageNum = pageNum;
	}
	public int getTotalRecordsCount() {
		return totalRecordsCount;
	}
	public void setTotalRecordsCount(int totalRecordsCount) {
		this.totalRecordsCount = totalRecordsCount;
	}
	public int getTotalPageNum() {
		return totalPageNum;
	}
	public void setTotalPageNum(int totalPageNum) {
		this.totalPageNum = totalPageNum;
	}
	public List<Customer> getCustomers() {
		return customers;
	}
	public void setCustomers(List<Customer> customers) {
		this.customers = customers;
	}
	
}

PageQuery.java

protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
		//拿到需要看的页是哪一页
		String pagenum = request.getParameter("pagenum");
		//调用业务层完成查询目标页的数据
		CustomerService cs=new CustomerService();
		//这里返回值是什么?
		//参考google和百度发现如果直接返回一个List集合(也就是当前页的目标数据),这样是远远不够的,因为还需要去做导航条等等一些信息
		//为了做分页,需要去引入一个新的JavaBean()设计一个JavaBean,来完成分页的数据的封装
		PageBean pageBean=cs.pageQuery(pagenum);
		
		//将pageBean存到request域对象中去,然后转发到jsp页面去显示
		request.setAttribute("pageBean", pageBean);
		request.getRequestDispatcher("/bean.jsp").forward(request, response);
	}

CustomerService

//完成分页查询的方法
	public PageBean pageQuery(String pagenum) {
		int numberPerPage=10;//默认的每页显示10条记录
		int pageNum=Integer.parseInt(pagenum);
		int totalRecordsCount=cdao.getTotalCount();
		
		int totalPageNum=(totalRecordsCount+numberPerPage-1)/numberPerPage;
		
		//select * from customers limit ?,?
		int startindex=(pageNum-1)*numberPerPage;
		List<Customer> customers=cdao.getCurrentPageData(startindex,numberPerPage);
		
		PageBean pageBean=new PageBean();
		pageBean.setNumberPerPage(numberPerPage);
		pageBean.setPageNum(pageNum);
		pageBean.setTotalRecordsCount(totalRecordsCount);
		pageBean.setTotalPageNum(totalPageNum);
		pageBean.setCustomers(customers);
		
		return pageBean;
	}

CustomerDaoImpl.java

//获得总记录条数
	@Override
	public int getTotalCount() {
		QueryRunner runner=new QueryRunner(JdbcUtils.getDataSource());
		try {
			long count= (long) runner.query("select count(*) from customers", new ScalarHandler());
			return (int) count;
		} catch (Exception e) {
			// TODO Auto-generated catch block
			throw new RuntimeException(e);
		}
		
	}
	//查询目标页的数据返回
	@Override
	public List<Customer> getCurrentPageData(int startindex, int numberPerPage) {
		QueryRunner runner=new QueryRunner(JdbcUtils.getDataSource());
		String sql="select * from customers limit ?,?";
		try {
			return runner.query(sql, new BeanListHandler<Customer>(Customer.class),startindex,numberPerPage);
		} catch (Exception e) {
			// TODO Auto-generated catch block
			throw new RuntimeException(e);
		}
	}

猜你喜欢

转载自blog.csdn.net/lln_lln/article/details/81046224