miracle just wanna be better

JDBC

2019-08-11
miracle

分页查询

假如设置每页只能有3条数据,我们有12条数据

currentPage 数据 PageSize
1 1-3 3
2 4-6 3
3 7-9 3
4 10-12 3

select * from t_user limit ?,?
第一个参数:是一个数字,从这个数字的下一条语句开始取数据
第二个参数:是一个数字,每一页取数据的条数
用下面公式

select * from table limit (currentPage-1)*pageSize,pageSize;

模糊查询

select * from t_user where username like '%a%' and address like '%a%'

分页+模糊

先模糊查询,然后对结果分页

select * from t_user where username like '%a%' limit (currentPage-1)*pageSize,pageSize               

分页模糊查询Demo

先创建OV包 Page.java

/**
 * vo类是程序中临时用的数据载体
 * @author PC
 *
 */
public class Page<T> {
	
	private int currentPage;//当前页
	private int pageSize;//每一页多少条数据
	private int preViousPage;//上一页
	private int nextPage;//下一页
	private int totalCount;//总记录数
	private int totalPage;//总页数
	
	private List<T> data;//当前页的数据
	
	private String[] keywords;//模糊的关键字

	public int getCurrentPage() {
		return currentPage;
	}

	public void setCurrentPage(int currentPage) {
		this.currentPage = currentPage;
	}

	public int getPageSize() {
		return pageSize;
	}

	public void setPageSize(int pageSize) {
		this.pageSize = pageSize;
	}

	public int getPreViousPage() {
		return preViousPage;
	}

	public void setPreViousPage(int preViousPage) {
		this.preViousPage = preViousPage;
	}

	public int getNextPage() {
		return nextPage;
	}

	public void setNextPage(int nextPage) {
		this.nextPage = nextPage;
	}

	public int getTotalCount() {
		return totalCount;
	}

	public void setTotalCount(int totalCount) {
		this.totalCount = totalCount;
	}

	public int getTotalPage() {
		return totalPage;
	}

	public void setTotalPage(int totalPage) {
		this.totalPage = totalPage;
	}

	public List<T> getData() {
		return data;
	}

	public void setData(List<T> data) {
		this.data = data;
	}

	public String[] getKeywords() {
		return keywords;
	}

	public void setKeywords(String[] keywords) {
		this.keywords = keywords;
	}

	@Override
	public String toString() {
		return "Page [currentPage=" + currentPage + ", pageSize=" + pageSize + ", preViousPage=" + preViousPage
				+ ", nextPage=" + nextPage + ", totalCount=" + totalCount + ", totalPage=" + totalPage + ", data="
				+ data + ", keywords=" + Arrays.toString(keywords) + "]";
	}
}

查找操作

//先得到一共有多少页
public Integer getCount2(Page<User> page) {
		Integer count=0;
		try {
			Connection con = CommonDao.getConnection();
			String sql = "select count(id) geshu from t_user " + "where username like ? and address like ?";
			PreparedStatement pstmt = con.prepareStatement(sql);
			pstmt.setString(1, "%" + page.getKeywords()[0] + "%");
			pstmt.setString(2, "%" + page.getKeywords()[1] + "%");
			ResultSet rs = pstmt.executeQuery();
			if (rs.next()) {
				count = rs.getInt("geshu");
			}
			CommonDao.closeAll(con, pstmt, rs);
		} catch (Exception e) {
			e.printStackTrace();
		}
		return count;
	}
    //查找得到数据
	@Override
	public List<User> getUsersByPage2(Page<User> page) {
		List<User> users=new ArrayList<User>();
		try {
			Connection con = CommonDao.getConnection();
			String sql = "select id,username uname,userpassword upwd,age,address from t_user "
					+ "where username like ? and address like ? " + "limit ?,?";
			PreparedStatement pstmt = con.prepareStatement(sql);
			pstmt.setString(1, "%" + page.getKeywords()[0] + "%");
			pstmt.setString(2, "%" + page.getKeywords()[1] + "%");
			pstmt.setInt(3, (page.getCurrentPage() - 1) * page.getPageSize());
			pstmt.setInt(4, page.getPageSize());
			ResultSet rs = pstmt.executeQuery();
			while (rs.next()) {
				User user = new User();
				user.setId(rs.getInt("id"));
				user.setName(rs.getString("uname"));
				user.setPassword(rs.getString("upwd"));
				user.setAge(rs.getInt("age"));
				user.setAddress(rs.getString("address"));
				users.add(user);
			}
			CommonDao.closeAll(con, pstmt, rs);
		} catch (Exception e) {
			// TODO: handle exception
		}
		return users;
	}

测试方法

public void testFindUsersByPage2(){
		
		UserDao userDao=new UserDaoMySql();
		Page page=new Page();
		page.setCurrentPage(1);//设置当前页为第一页
		page.setPageSize(3);//每页三个数据
		
		page.setKeywords(new String[]{"a","ch"});
		//查询数据库,获取总记录数
		int totalCount=userDao.getCount2(page);
		page.setTotalCount(totalCount);
		//计算总页数
		int totalPage=(totalCount%page.getPageSize()==0)? (totalCount/page.getPageSize()):(totalCount/page.getPageSize()+1);
		page.setTotalPage(totalPage);
		
		//计算前一页
		if(page.getCurrentPage()==1){
			page.setPreViousPage(1);
		}else{
			page.setPreViousPage(page.getCurrentPage()-1);
		}
		//计算后一页
		if(page.getCurrentPage()==totalPage){
			page.setNextPage(totalPage);
		}else{
			page.setNextPage(page.getCurrentPage()+1);
		}
		
		List<User> users=userDao.getUsersByPage2(page);
		page.setData(users);
		//到此为止page中的所有数据都存储完了
		System.out.println(page);
	}

jdbc项目构建

maven构建jdbc文件基本结构

通过java连接数据库的步骤如下:

创建实体包或vo包(value object)

  • 实体包和vo包都是用来存储数据的,实体类中的属性是私有的,通过公有的getter和setter存储和取出 数据
  • 实体包中放置的是实体类,每一个实体类恰好对应数据库中的一张表,每一个vo类是在程序中临时使用的数据,并不对应数据库中的表

创建dao包(data access object), 数据访问对象 dao包中的类都是用来操作数据库数据的

数据库的dml操作 增删改

  1. 开连接/获取连接
  2. 构建dml的sql语句
  3. 基于连接把dml的SQL语句运送到数据库,构建执行计划
    sql语句中有?占位符,给?占位符赋值
  4. 基于连接和完整的sql语句,调用executeUpdate()方法执行sql语句
    executeUpdate()方法的返回值为0,dml操作失败;返回值为1,dml操作成功
  5. 关闭连接和相关资源

    数据库的dql操作 查

  6. 开连接/获取连接
  7. 构建dql的sql语句
  8. 基于连接把dql的SQL语句运送到数据库,构建执行计划
    sql语句中有?占位符,给?占位符赋值
  9. 基于连接和完整的sql语句,调用executeQuery()方法执行sql语句
    executeQuery()方法的返回值为ResultSet(结果集)
  10. 循环遍历ResultSet中的数据,并把数据转存给缓存List集合
  11. 关闭连接和相关资源

单元测试 用java操作数据库数据的增删改查

基础方法(繁琐)

部分代码

开关连接

只将开连接和关闭连接的方法进行了复用,CommonDao.java只有两个方法

public class CommonDao {
	/**
	 * 获取连接的公共方法
	 * @return
	 * @throws Exception
	 */
	public static Connection getConnection()throws Exception{
		Connection con=null;
		Class.forName("com.mysql.jdbc.Driver");
		con=DriverManager.getConnection(
				"jdbc:mysql://localhost:3306/testdb",
				"root",
				"root");
		return con;
	}
	/**
	 * 关闭资源的公共方法
	 * @param con
	 * @throws Exception
	 */
	public static void closeAll(Connection con,PreparedStatement pstmt,ResultSet rs)throws Exception{
		if(con!=null){
			con.close();
		}
		if(pstmt!=null){
			pstmt.close();
		}
		if(rs!=null){
			rs.close();
		}
	}

}

增删改的方法(5步)

public Integer updateUser(User user) {
		int rowAffect=0;
		   try {
			//1.开连接/获取连接
			Connection con = CommonDao.getConnection();
			//2.构建dml的sql语句
			String sql = "update t_user set username=?,userpassword=?,age=?,address=? where id=?";
			//3.基于连接把dml的SQL语句运送到数据库,构建执行计划  sql语句中有?占位符,给?占位符赋值
			PreparedStatement pstmt = con.prepareStatement(sql);
			pstmt.setString(1, user.getName());
			pstmt.setString(2, user.getPassword());
			pstmt.setInt(3, user.getAge());
			pstmt.setString(4, user.getAddress());
			pstmt.setInt(5, user.getId());
			
			//4.基于连接和完整的sql语句,调用executeUpdate()方法执行sql语句
			rowAffect = pstmt.executeUpdate();
			//5.关闭连接和相关资源
			CommonDao.closeAll(con, pstmt,null);
		} catch (Exception e) {
			// TODO: handle exception
		}
		return rowAffect;
	}

查找数据的方法(6步)

public User findUserById(Integer id) {
		User user=null;
		try {
			
			//1.开连接/获取连接
			Connection con = CommonDao.getConnection();
			//2.构建dql的sql语句
			String sql = "select id,username,userpassword,age,address from t_user where id=?";
			//3.基于连接把dql的SQL语句运送到数据库,构建执行计划,sql语句中有?占位符,给?占位符赋值
			PreparedStatement pstmt = con.prepareStatement(sql);
			
			pstmt.setInt(1, id);
			//4.基于连接和完整的sql语句,调用executeQuery()方法执行sql语句
			ResultSet rs = pstmt.executeQuery();
			//5.循环遍历ResultSet中的数据,并把数据转存给缓存List集合
			if (rs.next()) {
				user=new User();
				user.setId(rs.getInt("id"));
				user.setName(rs.getString("username"));
				user.setPassword(rs.getString("userpassword"));
				user.setAge(rs.getInt("age"));
				user.setAddress(rs.getString("address"));
			}
			//6.关闭连接和相关资源
			CommonDao.closeAll(con, pstmt, rs);
			
		} catch (Exception e) {
			// TODO: handle exception
		}
		return user;
	}

策略设计模式JDBC

为了改进基础的方法,发现 executeUpdate()方法和executeQuery()方法都是通用的,所以在CommonDao里添加两个方法,把sql语句和params变量作为变量传入方法
executeUpdate()(增删改)的操作整合

/**
	 * 通用dml操作
	 * @param sql dml sql语句
	 * @param params dml sql语句的问号?占位符
	 * @return 受影响的行数
	 */
	public static int executeUpdate(String sql,Object...params)throws Exception{
		int rowAffect=0;
		Connection con=getConnection();
		PreparedStatement pstmt=con.prepareStatement(sql);
		//通过此方式给?占位符传值
		if(params!=null){
			for(int i=0;i<params.length;i++){
				pstmt.setObject(i+1, params[i]);
			}
		}
		rowAffect=pstmt.executeUpdate();
		closeAll(con,pstmt,null);
		return rowAffect;
	}

executeQuery()方法(查询)还需要对ResultSet里的数据进行操作,而且不同的查询方法对不同的ResultSet里的数据操作不一样,所以我们想可以用接口作为参数的方式传入方法,子类重写此接口的方法,来完成不同的查找功能 先定义一个接口,接口只有一个方法,是泛型接口,返回值为T类型,参数为ResultSet对象,返回类型是Type型
所以返回值可以是程序员定义的一切类,比如Product类,User类都可以,在这个例子里就是User类,就可以把存完值后的User对象返回

public interface RowMapper<T> {
	//此方法专门用来做结果集和实体对象的映射关系
	public T mapRow(ResultSet rs)throws SQLException;
}

下面写CommonDao.java中的executeQuery方法,来看是如何使用这个接口来传值的

/**
	 * 策略版本的通用查询
	 * @param rm 处理结果集的策略
	 * @param sql  dql的sql语句
	 * @param params  dql的参数
	 * @return List集合
	 * @throws Exception
	 */
	public static <T> List<T> executeQuery(RowMapper<T> rm,String sql,Object...params)throws Exception{
		List<T> list = new ArrayList<T>();
		Connection con=getConnection();
		PreparedStatement pstmt=con.prepareStatement(sql);
		if(params!=null){
			for(int i=0;i<params.length;i++){
				pstmt.setObject(i+1, params[i]);
			}
		}
		ResultSet rs=pstmt.executeQuery();
		while(rs.next()){
			T t=rm.mapRow(rs);//接口的对象.子类重写后的方法,返回值可以是任何定义的类
			list.add(t);
		}
		closeAll(con,pstmt,rs);
		return list;
	}

下面代码使用这个方法

public User findUserById(Integer id) {
		List<User> list = new ArrayList<User>();

		try {
			String sql = "select id,username,userpassword,age,address from t_user where id=?";
			RowMapper<User> rm = new RowMapper<User>() {

				@Override
				public User mapRow(ResultSet rs) throws SQLException {
					User user = new User();
					user.setName(rs.getString("username"));
					user.setId(rs.getInt("id"));
					user.setPassword(rs.getString("userpassword"));
					user.setAge(rs.getInt("age"));
					user.setAddress(rs.getString("address"));
					return user;
				}

			};
			list = CommonDao.executeQuery(rm, sql, id);//params占位符就是方法的参数id
		} catch (Exception e) {
			// TODO: handle exception
		}
		return list.get(0);//查找的数据只有一条,所以取第一个
	}

反射模式

也对基础方法进行了改进,把相同的操作放到同一个方法中,使用反射的方式来实现,因为开始要建立实体类,实体类中有很多属性(成员变量),我们把实体类的属性名字和数据库的属性名字设置为一样的,就可以通过查找到数据库的ResultSetMetaData的columnName(列名字)来为实体类赋值,查到ResultSet后,通过set列名字==(set属性名)把数据存到类的对象里,先通过列名构建setter方法,通过反射去类里查找对应的方法,通过此方法为类对象赋值


 * 反射版本的通用查询
 * 必须要求结果集的列名字跟实体中的setter方法匹配
 * @param clazz 
 * @param sql  dqlsql语句
 * @param params  dql的参数
 * @return List集合
 * @throws Exception
 */
public static <T> List<T> executeQuery(Class<T> clazz,String sql,Object...params)throws Exception{
	List<T> list = new ArrayList<T>();
	Connection con=getConnection();
	PreparedStatement pstmt=con.prepareStatement(sql);
	if(params!=null){
		for(int i=0;i<params.length;i++){
			pstmt.setObject(i+1, params[i]);
		}
	}
	//获取结果集对象
	ResultSet rs=pstmt.executeQuery();
	//获取结果集的元数据(meta data)
	
	ResultSetMetaData rsmd=rs.getMetaData();
	//从结果集的元数据中获取相应的元数据,比如结果集的列名字
	List<String> columnNames=new ArrayList<String>();
	for(int i=0;i<rsmd.getColumnCount();i++){
		columnNames.add(rsmd.getColumnLabel(i+1));
	}
	//select id,username name,userpassword password,age,address from t_user
	//columnNames:[id,name,password,age,address]
	while(rs.next()){
		T t=clazz.newInstance();//实例化实体对象,类比为user
		//利用反射机制给t对象set数据,数据从rs中getObject()
		for(String columnName:columnNames){
			//此for循环,一次循环构建一个setter方法
			//setId setName setPassword setAge setAddress
			String setterName="set"+columnName.substring(0,1).toUpperCase()+columnName.substring(1);
			Field field = clazz.getDeclaredField(columnName);//反射获取该属性(数据库列名和实体属性名一样)
			Method method=clazz.getDeclaredMethod(setterName, field.getType());//反射得到该方法
			Object value=rs.getObject(columnName);
			
			/*//遍历所有方法,找到对应的方法,循环的方法效率较低
			Method[] methods=clazz.getDeclaredMethods();
			for(Method method:methods){
				if(setterName.equals(method.getName())){
					//执行到这说明能找到实体类中对应的setter方法
					//从rs中当前行指定columnName列的数据
					Object value=rs.getObject(columnName);*/
			
					//处理value是聚合函数的值,因为value在结果集中是Long类型
					if(value instanceof Long){
						Long l=(Long)value;
						value=l.intValue();
					}
					
					//处理Oracle的id字段为number类型,进入到结果集中就变成了BigDecimal
					if(value instanceof BigDecimal){
						BigDecimal bd=(BigDecimal)value;
						value=bd.intValue();
					}
					
					
					//调用setter方法把结果集中的数据存到t对象
					method.invoke(t, value);
				
			
			
		}
		list.add(t);
	}
	closeAll(con,pstmt,rs);
	return list;
}

Statement接口和PreparedStatement接口的区别

相同点:都可以构建sql语句,都可以创建并执行这个计划
不同点:Statement主要用于执行静态sql语句,即sql语句内容不变的语句

  • Statement接口 每执行一次都要传入sql语句,并编译一次,即创建一次执行计划 比如:
 String sql = select * from t_user where id=1;
 stmp.executeQuery(sql);
 String sql2 = select * from t_user where id=2;
 stmp.executeQuery(sql2);

上面做了两个执行计划

  • PreparedStatement接口:
    对传入的sql语句预先编译,预先创建执行计划,一个执行计划可以执行多个sql语句,效率较高
    数据库给上面的sql语句创建执行计划
    ?号的占位符,可以给不同的数据,但执行计划用的是同一个

避免SQL注入

登录的sql语句

  String sql="select id from t_user where username='张三' and userpassword='zs'"  

用Statement接口

stmt.executeQuery(String sql="select id from t_user where username='"+uname+"' and userpassword='"+upwd+"'"); 

如果密码此时输入的是String upwd=” a’ or ‘b’=’b”; 判断正确了,可以登录,用PreparedStatement接口,?必须是一个值,所以就不能判定

事务管理:

事务的4大特性:简称acid

  1. 原子性(atomicity) 原子性是指事务包含的所有操作要么全部成功,要么全部失败回滚 比如: 以银行转账为例,一次转账包含两个update更新语句
update t_balance set 余额=余额-1000 where card_id='11111';
update t_balance set 余额=余额+1000 where card_id='22222';
  1. 一致性(consistency) 一致性是指事务必须使数据库从一个一致性状态变换到另一个一致性的状态 也就是说一个事务执行之前和执行之后必须处于一致状态 比如: 以银行转账为例,转账前两个账户金额和为20000,转账后金额的和还是20000 无论怎么转账,前后的总和必须一致
  2. 隔离性(Isolation) 隔离性是当前多个用户并发访问数据库的时候,数据库为每一个用户开启事务 其中的某一个事务不能被其他事务操作所干扰,多个并发事务之间要相互隔离 即要达到这么一个效果:对于任意两个并发的事务T1和T2,在事务T1看来, T2要么在T1开始之间就已经结束,要么在T1结束之后才开始执行
  3. 持久性(Durability) 持久性是指一个事务一旦提交了,那么对数据库中的数据的改变就是永久性的 即便是在数据库系统中遇到故障的情况下也不会丢失提交事务的操作

数据库的四种隔离级别:

  1. Serializable(串行化):可以避免脏读,不可重复读,幻读的发生
  2. repeatable read(可重复读):可避免脏读,不可重复读的发生
  3. read committed(读已提交):可避免脏读的发生
  4. read uncommitted(读未提交):最低的级别,任何情况下都可能出问题 以上四种隔离级别最高serializable,最低的read uncommitted,隔离级别越高,执行效率就低 ,mysql的默认隔离级别repeatable read 可重复读 ,oracle数据库只支持串行化级别和读已提交两种级别,默认的是读已提交级别

因为有了并发事务,多个事务同时执行,根据数据库的隔离级别,就会产生如下问题

  • 脏读:
    指在一个事务处理过程中读取另一个未提交的事务中的数据 比如:以银行转账为例:
update t_balance set 余额=余额-1000 where card_id='11111';
update t_balance set 余额=余额+1000 where card_id='22222';

如果发生了这样的情况就脏读 在执行完第一个更新语句的时候,但是还没有执行第二个更新语句,另一个事务中读取t_balance表的数据,余额被减,通知用户余额减过了,还没有执行第二条更新语句就回滚了,此时已经提示过余额减过了

  • 不可重复读:
    针对数据库中某个数据,一个事务范围内多次查询却返回不同的数据值,这是由于在查询的间隔,被另外一个事务把数据修改并提交了 比如: 事务T1在读取某一个数据,而事务T2修改了这个数据,并且提交了事务, 事务T1再次读取这个数据,就与上一次T1事务读取值的不同,发生不可重复读

不可重复读和脏读的区别:

脏读是某一个事务读取了另一个事务未提交的脏数据,
不可重复读则是读取了上一个事务提交的数据

注意:在某些情况下,不可重复读不是问题,无论数据被改了多少次且提交,只需要获得最后提交的数据即可
幻读:事务非独立执行时发生的一种现象
比如:
事务T1对一个表中的所有的行的某个数据项做了从”1”修改数据”2”的操作,这时事务T2又对这个表添加一行数据,而这行中指定的列的数据项是”1”,并且提交给数据库,这时操作事务T1的用户查询此数据表,就会发现有一行,没有从”1”修改到”2”,此种情况就是幻读

幻读和不可重复读都是读取了另一个已经提交的事务
脏读是读取的另一个未提交的事务数据
不可重复读查询的都是同一个数据项
而幻读针对是一批数据的整体中的某个数据项

在mysql中如何设置隔离级别

  1. 在mysql中设置 在mysql的黑白界面中
    set tx_isolation=’隔离级别名称’
  2. 在java代码中设置 设置setAutoCommit(false),发生异常时con.rollback(),正常执行时con.commit()
Connection con=null;
try{
  con=CommonDao.getConnection();
  //设置数据库的隔离级别,只对当前连接有效
  con.setTransactionIsolation(Connection.TRANSACTION_SERIALIZABLE);
  con.setAutoCommit(false);开始事务
  ...
  con.commit();
}catch(Excetption e){
    con.rollback()
	e.printStaceTrace();
}   

JDBC三种方式完整代码


Similar Posts

上一篇 MYSQL常用语句

下一篇 Servlet

Comments