学习目标
- 能够说出什么是数据库元数据
- 掌握自定义数据库框架,实现增加、删除、更新方法
- 掌握JdbcTemplate实现增删改
- 掌握JdbcTemplate实现增查询
- 能够理解分层的作用
- 掌握使用三层架构实现用户注册案例
第1章 数据库元数据
1.1 元数据的基本概述
元数据:数据库、表、列的定义信息。
1.2 ParameterMetaData
ParameterMetaData
可用于获取有关PreparedStatement
对象中每个参数标记的类型和属性。
select * from user where name=? and password=?
// ParameterMetaData可以用来获取?的个数和类型
1.2.1 如何获取ParameterMetaData
通过PreparedStatement
的getParameterMetaData()
方法来获取到ParameterMetaData
对象
1.2.2 API介绍
-
int getParameterCount() 获取PreparedStatement的SQL语句参数?的个数
-
int getParameterType(int param) 获取指定参数的SQL类型。
1.2.3 使用步骤
- 获取
ParameterMetaData
对象 - 使用对象调用方法
1.2.4 注意事项
- 不是所有的数据库驱动都能后去到参数类型(MySQL会出异常)
1.2.5 案例代码
public class Demo01 {
public static void main(String[] args) throws Exception {
Connection conn = DataSourceUtils.getConnection();
String sql = "INSERT INTO student (name, age, score) VALUES (?, ?, ?)";
PreparedStatement stmt = conn.prepareStatement(sql);
ParameterMetaData md = stmt.getParameterMetaData();
System.out.println("参数个数: " + md.getParameterCount());
// Parameter metadata not available for the given statement
// MySQL不支持获取参数类型
// System.out.println("参数类型: " + md.getParameterType(1));
}
}
1.3 ResultSetMetaData
ResultSetMetaData
可用于获取有关ResultSet
对象中列的类型和属性的信息。
1.3.1 如何获取ResultSetMetaData
通过ResultSet
的getMetaData()
方法来获取到ResultSetMetaData
对象
1.3.2 API介绍
-
int getColumnCount() 返回此 ResultSet对象中的列数
-
String getColumnName(int column) 获取指定列的名称
-
String getColumnTypeName(int column) 获取指定列的数据库特定类型名称
1.3.3 使用步骤
- 获取
ResultSetMetaData
对象 - 使用对象调用方法
1.3.4 案例代码
// ResultSetMetaData
public static void test02() throws SQLException {
Connection conn = DataSourceUtils.getConnection();
String sql = "SELECT * FROM student WHERE id=1";
PreparedStatement stmt = conn.prepareStatement(sql);
ResultSet rs = stmt.executeQuery();
// 获取结果集元数据
ResultSetMetaData md = rs.getMetaData();
int num = md.getColumnCount();
System.out.println("列数:" + num);
for (int i = 0; i < num; i++) {
System.out.println("列名:" + md.getColumnName(i + 1)); // 获取列名
System.out.println("列类型:" + md.getColumnTypeName(i + 1)); // 获取类的类型
System.out.println("-----------");
}
}
1.3.5 案例效果
1.4 案例自定义数据库框架,实现增加、删除、更新方法
目前使用连接池工具类操作数据库的代码
// 增加数据
public static void testInsert() throws SQLException {
// 获取连接
Connection conn = DataSourceUtils.getConnection();
String sql = "INSERT INTO student (name, age, score) VALUES (?, ?, ?);";
PreparedStatement pstmt = conn.prepareStatement(sql);
// 设置参数
pstmt.setString(1, "刘德华");
pstmt.setInt(2, 57);
pstmt.setInt(3, 99);
int i = pstmt.executeUpdate();
System.out.println("影响的行数:" + i);
// 关闭连接
DataSourceUtils.close(conn, pstmt);
}
// 修改数据
public static void testUpdate() throws SQLException {
Connection conn = DataSourceUtils.getConnection();
String sql = "UPDATE student SET name=? WHERE id=?;";
PreparedStatement pstmt = conn.prepareStatement(sql);
pstmt.setString(1, "张学友");
pstmt.setInt(2, 2);
int i = pstmt.executeUpdate();
System.out.println("影响的行数:" + i);
DataSourceUtils.close(conn, pstmt);
}
// 删除数据
public static void testDelete() throws SQLException {
Connection conn = DataSourceUtils.getConnection();
String sql = "DELETE FROM student WHERE id=?;";
PreparedStatement pstmt = conn.prepareStatement(sql);
pstmt.setInt(1, 3);
int i = pstmt.executeUpdate();
System.out.println("影响的行数:" + i);
DataSourceUtils.close(conn, pstmt);
}
存在的问题:我们发现,增删改都需要获取连接,关闭连接,只是SQL语句不同。我们可以使用元数据来自动给SQL设置参数,合并成一个方法。
在DataSourceUtils
中增加一个方法update
/*
String sql: sql语句
Object[] params: 参数数组
*/
public static int update(String sql, Object[] params) {
Connection conn = null;
PreparedStatement st = null;
try {
conn = getConnection();
st = conn.prepareStatement(sql);
// 获取到参数个数
ParameterMetaData md = st.getParameterMetaData();
int num = md.getParameterCount(); // 参数的个数
for (int i = 0; i < num; i++) {
// 将参数赋值给对应的?号
st.setObject(i + 1, params[i]);
}
return st.executeUpdate();
} catch (SQLException e) {
e.printStackTrace();
} finally {
close(conn, st);
}
return 0;
}
增删改都调用update
方法,只需要编写SQL语句和设置参数即可,可以减少代码。
// 增加数据
public static void testInsert2() throws SQLException {
String sql = "INSERT INTO student (name, age, score) VALUES (?, ?, ?);";
Object[] params = {"郭富城", 55, 88};
int i = DataSourceUtils.update(sql, params);
System.out.println("影响的行数:" + i);
}
// 修改数据
public static void testUpdate2() throws SQLException {
String sql = "UPDATE student SET name=? WHERE id=?;";
Object[] params = {"黎明", 1};
int i = DataSourceUtils.update(sql, params);
System.out.println("影响的行数:" + i);
}
// 删除数据
public static void testDelete2() throws SQLException {
String sql = "DELETE FROM student WHERE id=?;";
Object[] params = {7};
int i = DataSourceUtils.update(sql, params);
System.out.println("影响的行数:" + i);
}
第2章 JdbcTemplate
2.1 JdbcTemplate概念
JDBC已经能够满足大部分用户最基本的需求,但是在使用JDBC时,必须自己来管理数据库资源如:获取PreparedStatement,设置SQL语句参数,关闭连接等步骤。JdbcTemplate就是Spring对JDBC的封装,目的是使JDBC更加易于使用。JdbcTemplate是Spring的一部分。
JdbcTemplate处理了资源的建立和释放。他帮助我们避免一些常见的错误,比如忘了总要关闭连接。他运行核心的JDBC工作流,如Statement的建立和执行,而我们只需要提供SQL语句和提取结果。
Spring源码地址:https://github.com/spring-projects/spring-framework
在JdbcTemplate中执行SQL语句的方法大致分为3类:
execute
:可以执行所有SQL语句,一般用于执行DDL语句。update
:用于执行INSERT
、UPDATE
、DELETE
等DML语句。queryXxx
:用于DQL数据查询语句。
2.2 JdbcTemplate使用过程
2.2.1 Druid基于配置文件实现连接池
2.2.1.1 API介绍
org.springframework.jdbc.core.JdbcTemplate
类方便执行SQL语句
-
public JdbcTemplate(DataSource dataSource) 创建JdbcTemplate对象,方便执行SQL语句
-
public void execute(final String sql) execute可以执行所有SQL语句,因为没有返回值,一般用于执行DDL语句。
2.2.1.2 使用步骤
- 准备DruidDataSource连接池
- 导入依赖的jar包
spring-beans-4.1.2.RELEASE.jar
spring-core-4.1.2.RELEASE.jar
spring-jdbc-4.1.2.RELEASE.jar
spring-tx-4.1.2.RELEASE.jar
com.springsource.org.apache.commons.logging-1.1.1.jar
- 创建
JdbcTemplate
对象,传入Druid
连接池 - 调用
execute
、update
、queryXxx
等方法
2.2.1.3 案例代码
public class Demo04 {
public static void main(String[] args) {
// 创建表的SQL语句
String sql = "CREATE TABLE product("
+ "pid INT PRIMARY KEY AUTO_INCREMENT,"
+ "pname VARCHAR(20),"
+ "price DOUBLE"
+ ");";
JdbcTemplate jdbcTemplate = new JdbcTemplate(JdbcUtils.getDataSource());
jdbcTemplate.execute(sql);
}
}
2.2.1.4 案例效果
- 代码效果
- 执行SQL后创建数据库效果
2.3 JdbcTemplate实现增删改
2.3.1 API介绍
org.springframework.jdbc.core.JdbcTemplate
类方便执行SQL语句
-
public int update(final String sql) 用于执行`INSERT`、`UPDATE`、`DELETE`等DML语句。
2.3.2 使用步骤
1.创建JdbcTemplate对象
2.编写SQL语句
3.使用JdbcTemplate对象的update方法进行增删改
2.3.3 案例代码
public class Demo05 {
public static void main(String[] args) throws Exception {
// test01();
// test02();
// test03();
}
// JDBCTemplate添加数据
public static void test01() throws Exception {
JdbcTemplate jdbcTemplate = new JdbcTemplate(JdbcUtils.getDataSource());
String sql = "INSERT INTO product VALUES (NULL, ?, ?);";
jdbcTemplate.update(sql, "iPhone3GS", 3333);
jdbcTemplate.update(sql, "iPhone4", 5000);
jdbcTemplate.update(sql, "iPhone4S", 5001);
jdbcTemplate.update(sql, "iPhone5", 5555);
jdbcTemplate.update(sql, "iPhone5C", 3888);
jdbcTemplate.update(sql, "iPhone5S", 5666);
jdbcTemplate.update(sql, "iPhone6", 6666);
jdbcTemplate.update(sql, "iPhone6S", 7000);
jdbcTemplate.update(sql, "iPhone6SP", 7777);
jdbcTemplate.update(sql, "iPhoneX", 8888);
}
// JDBCTemplate修改数据
public static void test02() throws Exception {
JdbcTemplate jdbcTemplate = new JdbcTemplate(JdbcUtils.getDataSource());
String sql = "UPDATE product SET pname=?, price=? WHERE pid=?;";
int i = jdbcTemplate.update(sql, "XVIII", 18888, 10);
System.out.println("影响的行数: " + i);
}
// JDBCTemplate删除数据
public static void test03() throws Exception {
JdbcTemplate jdbcTemplate = new JdbcTemplate(JdbcUtils.getDataSource());
String sql = "DELETE FROM product WHERE pid=?;";
int i = jdbcTemplate.update(sql, 7);
System.out.println("影响的行数: " + i);
}
}
2.3.4 案例效果
- 增加数据效果
- 修改数据效果
- 删除数据效果
2.3.5 总结
JdbcTemplate的update
方法用于执行DML语句。同时还可以在SQL语句中使用?占位,在update方法的Object... args
可变参数中传入对应的参数。
2.4 JdbcTemplate实现查询
org.springframework.jdbc.core.JdbcTemplate
类方便执行SQL语句
2.4.1 queryForInt返回一个整数
2.4.1.1 API介绍
-
public int queryForInt(String sql) 执行查询语句,返回一个int类型的值。
2.4.1.2 使用步骤
- 创建JdbcTemplate对象
- 编写查询的SQL语句
- 使用JdbcTemplate对象的queryForInt方法
- 输出结果
2.4.1.3 案例代码
// queryForInt返回一个整数
public static void test01() throws Exception {
// String sql = "SELECT COUNT(*) FROM product;";
String sql = "SELECT pid FROM product WHERE price=18888;";
JdbcTemplate jdbcTemplate = new JdbcTemplate(JdbcUtils.getDataSource());
int forInt = jdbcTemplate.queryForInt(sql);
System.out.println(forInt);
}
2.4.1.4 案例效果
2.4.2 queryForLong返回一个long类型整数
2.6.2.1 API介绍
-
public long queryForLong(String sql) 执行查询语句,返回一个long类型的数据。
2.4.2.2 使用步骤
- 创建JdbcTemplate对象
- 编写查询的SQL语句
- 使用JdbcTemplate对象的queryForLong方法
- 输出结果
2.4.2.3 案例代码
// queryForLong 返回一个long类型整数
public static void test02() throws Exception {
String sql = "SELECT COUNT(*) FROM product;";
// String sql = "SELECT pid FROM product WHERE price=18888;";
JdbcTemplate jdbcTemplate = new JdbcTemplate(JdbcUtils.getDataSource());
long forLong = jdbcTemplate.queryForLong(sql);
System.out.println(forLong);
}
2.4.2.4 案例效果
2.4.3 queryForObject返回String
2.4.3.1 API介绍
-
public <T> T queryForObject(String sql, Class<T> requiredType) 执行查询语句,返回一个指定类型的数据。
2.4.3.2 使用步骤
- 创建JdbcTemplate对象
- 编写查询的SQL语句
- 使用JdbcTemplate对象的queryForObject方法,并传入需要返回的数据的类型
- 输出结果
2.4.3.3 案例代码
public static void test03() throws Exception {
String sql = "SELECT pname FROM product WHERE price=7777;";
JdbcTemplate jdbcTemplate = new JdbcTemplate(JdbcUtils.getDataSource());
String str = jdbcTemplate.queryForObject(sql, String.class);
System.out.println(str);
}
2.4.3.4 案例效果
2.4.4 queryForMap返回一个Map集合对象
2.4.4.1 API介绍
-
public Map<String, Object> queryForMap(String sql) 执行查询语句,将一条记录放到一个Map中。
2.4.4.2 使用步骤
- 创建JdbcTemplate对象
- 编写查询的SQL语句
- 使用JdbcTemplate对象的queryForMap方法
- 处理结果
2.4.4.3 案例代码
public static void test04() throws Exception {
String sql = "SELECT * FROM product WHERE pid=?;";
JdbcTemplate jdbcTemplate = new JdbcTemplate(JdbcUtils.getDataSource());
Map<String, Object> map = jdbcTemplate.queryForMap(sql, 6);
System.out.println(map);
}
2.4.4.4 案例效果
2.4.5 queryForList返回一个List集合对象,集合对象存储Map类型数据
2.4.5.1 API介绍
-
public List<Map<String, Object>> queryForList(String sql) 执行查询语句,返回一个List集合,List中存放的是Map类型的数据。
2.4.5.2 使用步骤
- 创建JdbcTemplate对象
- 编写查询的SQL语句
- 使用JdbcTemplate对象的queryForList方法
- 处理结果
2.4.5.3 案例代码
public static void test05() throws Exception {
String sql = "SELECT * FROM product WHERE pid<?;";
JdbcTemplate jdbcTemplate = new JdbcTemplate(JdbcUtils.getDataSource());
List<Map<String, Object>> list = jdbcTemplate.queryForList(sql, 8);
for (Map<String, Object> map : list) {
System.out.println(map);
}
}
2.4.5.4 案例效果
2.4.6 query使用RowMapper做映射返回对象
2.4.6.1 API介绍
-
public <T> List<T> query(String sql, RowMapper<T> rowMapper) 执行查询语句,返回一个List集合,List中存放的是RowMapper指定类型的数据。
2.4.6.2 使用步骤
- 定义Product类
- 创建JdbcTemplate对象
- 编写查询的SQL语句
- 使用JdbcTemplate对象的query方法,并传入RowMapper匿名内部类
- 在匿名内部类中将结果集中的一行记录转成一个Product对象
2.4.6.3 案例代码
// query使用rowMap做映射返回一个对象
public static void test06() throws Exception {
JdbcTemplate jdbcTemplate = new JdbcTemplate(JdbcUtils.getDataSource());
// 查询数据的SQL语句
String sql = "SELECT * FROM product;";
List<Product> query = jdbcTemplate.query(sql, new RowMapper<Product>() {
@Override
public Product mapRow(ResultSet arg0, int arg1) throws SQLException {
Product p = new Product();
p.setPid(arg0.getInt("pid"));
p.setPname(arg0.getString("pname"));
p.setPrice(arg0.getDouble("price"));
return p;
}
});
for (Product product : query) {
System.out.println(product);
}
}
2.4.6.4 案例效果
2.4.7 query使用BeanPropertyRowMapper做映射返回对象
2.4.7.1 API介绍
-
public <T> List<T> query(String sql, RowMapper<T> rowMapper) 执行查询语句,返回一个List集合,List中存放的是RowMapper指定类型的数据。
-
public class BeanPropertyRowMapper<T> implements RowMapper<T> BeanPropertyRowMapper类实现了RowMapper接口
2.4.7.2 使用步骤
- 定义Product类
- 创建JdbcTemplate对象
- 编写查询的SQL语句
- 使用JdbcTemplate对象的query方法,并传入BeanPropertyRowMapper对象
2.4.7.3 案例代码
// query使用BeanPropertyRowMapper做映射返回对象
public static void test07() throws Exception {
JdbcTemplate jdbcTemplate = new JdbcTemplate(JdbcUtils.getDataSource());
// 查询数据的SQL语句
String sql = "SELECT * FROM product;";
List<Product> list = jdbcTemplate.query(sql, new BeanPropertyRowMapper<>(Product.class));
for (Product product : list) {
System.out.println(product);
}
}
2.4.6.4 案例效果
2.4.8 总结
JDBCTemplate的query
方法用于执行SQL语句,简化JDBC的代码。同时还可以在SQL语句中使用?
占位,在query
方法的Object... args
可变参数中传入对应的参数。
第3章 三层架构
3.1 分层的作用
我们之前的登录案例是将用户输入,数据库的操作,逻辑处理放在了同一个方法中,这样虽然非常直观,但是等项目做大的时候非常不好维护代码,也不好增加功能。
分层介绍:
公司中分层:
软件中分层:按照不同功能分为不同层,通常分为三层:表示层,业务层,数据访问层。
分层的作用:
1. 解耦:降低层与层之间的耦合性。
2. 可维护性:提高软件的可维护性,对现有的功能进行修改和更新时不会影响原有的功能。
3. 可扩展性:提升软件的可扩展性,添加新的功能的时候不会影响到现有的功能。
4. 可重用性:不同层之间进行功能调用时,相同的功能可以重复使用。
分层的方法:不同层次使用不同的包。
开发步骤:
从下向上开发:dao数据访问层 -> service业务逻辑层 -> view表示层
3.2 使用三层架构实现用户注册案例
3.2.1 案例需求
使用分层实现用户注册案例
3.2.2 案例效果
3.2.3 案例分析
- 使用数据库保存用户的账号和密码
- 定义User类
- 编写DAO层,增加
saveUser
和findUser
方法 - 编写业务层增加
register
和isExist
方法 - 编写View层增加
register
方法
3.2.4 实现步骤
- 使用数据库保存用户的账号和密码,创建user表保存账号密码
CREATE TABLE `user` (
uid INT PRIMARY KEY AUTO_INCREMENT,
uname VARCHAR(20),
upasswd VARCHAR(20)
);
INSERT INTO `user` (uname, upasswd) VALUES ('abc', '123');
INSERT INTO `user` (uname, upasswd) VALUES ('efg', '123');
INSERT INTO `user` (uname, upasswd) VALUES ('admin', '123');
- 定义User类
/**
* 用户类
*
*/
public class User {
private int uid;
private String uname;
private String upasswd;
public int getUid() {
return uid;
}
public void setUid(int uid) {
this.uid = uid;
}
public String getUname() {
return uname;
}
public void setUname(String uname) {
this.uname = uname;
}
public String getUpasswd() {
return upasswd;
}
public void setUpasswd(String upasswd) {
this.upasswd = upasswd;
}
public User(int uid, String uname, String upasswd) {
super();
this.uid = uid;
this.uname = uname;
this.upasswd = upasswd;
}
public User(String uname, String upasswd) {
super();
this.uname = uname;
this.upasswd = upasswd;
}
public User() {
super();
}
@Override
public String toString() {
return "User [uid=" + uid + ", uname=" + uname + ", upasswd=" + upasswd + "]";
}
}
- 编写DAO层,增加
saveUser
和findUser
方法
public class UserDao {
JdbcTemplate jdbcTemplate = new JdbcTemplate(DataSourceUtils.getDataSource());
// 保存用户到数据库
public int saveUser(User user) {
try {
return jdbcTemplate.update("INSERT INTO user (uname, upasswd) values (?, ?);", user.getUname(), user.getUpasswd());
} catch (Exception e) {
e.printStackTrace();
throw new RuntimeException(e);
}
}
// 从数据库查询是否有相同的记录
public User findUser(String uname) {
try {
String sql = "SELECT * FROM user WHERE uname = ?;";
// 注意:queryForObject如果没有找到数据,不是返回null,而是抛出异常,所以捕获异常,返回null
return jdbcTemplate.queryForObject(sql, new BeanPropertyRowMapper<User>(User.class), uname);
} catch (Exception e) {
return null;
}
}
}
- 编写业务层增加
register
和isExist
方法
/**
* 业务层
*
*/
public class UserService {
private UserDao userDao = new UserDao();
/**
* 注册
*/
public boolean register(User user) {
// 保存用户信息到数据库
return userDao.saveUser(user) > 0;
}
/**
* 判断用户是否存在
*/
public boolean isExist(String name) {
return userDao.findUser(name) != null;
}
}
- 编写View层增加
register
方法
/*
view表示层
*/
public class UserSystem {
private static UserService userService = new UserService();
private static Scanner scanner = new Scanner(System.in);
public static void main(String[] args) {
System.out.println("-- 欢迎使用 --");
while (true) {
// 循环注册,知道注册成功才结束
if (register()) {
break;
}
}
}
/**
* 注册的方法
*/
private static boolean register() {
System.out.println("请输入注册信息:");
System.out.println("用户名:");
String name = scanner.next();
if (userService.isExist(name)) {
System.out.println("用户名已经存在");
return false;
}
System.out.println("密码:");
String password = scanner.next();
//封装数据
User user = new User(name, password);
if (userService.register(user)) {
System.out.println("注册成功");
return true;
} else {
System.out.println("注册失败");
return false;
}
}
}
3.2.5 总结
当我们使用分层后,修改代码很方便,假如SQL语句写的有问题,只需要修改DAO层。使用分层后,增加功能也很方便,比如增加登录功能,在视图层增加登录的界面,在业务层增加登录的逻辑,DAO层直接使用原有代码。
分层的作用:
1. 解耦:降低层与层之间的耦合性。
2. 可维护性:提高软件的可维护性,对现有的功能进行修改和更新时不会影响原有的功能。
3. 可扩展性:提升软件的可扩展性,添加新的功能的时候不会影响到现有的功能。
4. 可重用性:不同层之间进行功能调用时,相同的功能可以重复使用。