1. 数据库元数据
1.1 元数据概念
- 数据库、表、列的定义信息。
1.2 ParameterMetaData参数元数据
ParameterMetaData
用于获取有关PreparedStatement
对象中每个参数标记的类型和属性。
1.2.1 获取ParameterMetaData对象
- 通过
PreparedStatement
的getParameterMetaData()
方法获取
1.2.2 ParameterMetaData常用方法
- int getParameterCount(); 获取PreparedStatement的SQL语句中?的个数
- int getParameterType(int param); 获取指定参数的SQL类型
注意:不是所有的数据库驱动都能获取参数类型(MySQL会出异常)
1.3 ResultSetMetaData结果集元数据
ResultSetMetaData
用于获取有关ResultSet
对象中列的类型和属性的信息。
1.3.1 获取ResultSetMetaData对象
- 通过
ResultSet
的getMetaData()
方法来获取
1.3.2 ResultSetMetaData常用方法
- int getColumnCount(); 返回此 ResultSet对象中的列数
- String getColumnName(int column); 获取指定列的名称
- String getColumnTypeName(int column); 获取指定列的数据库特定类型名称
1.4 示例案例(自定义框架)
需求:使用连接池工具类实现数据库的增删改,该工具类都只需要传入SQL与参数即可
目的:使用元数据将增删改的方法合并成一个
import Utils.JdbcUtils;
import java.sql.Connection;
import java.sql.ParameterMetaData;
import java.sql.PreparedStatement;
import java.sql.SQLException;
public class Notes01 {
//实现直接传入sql语句和参数
public static void update(String sql,Object...args) throws SQLException {
//获取一个连接
Connection connection = JdbcUtils.getConnection();
//获取一个预编译对象
PreparedStatement pst = connection.prepareStatement(sql);
//获得元数据对象
ParameterMetaData pmd = pst.getParameterMetaData();
//获取当前sql语句需要设置的参数个数
int count = pmd.getParameterCount();
//设置参数
for (int i = 0; i < count; i++) {
pst.setObject(i+1,args[i]);
}
//执行SQL语句
pst.executeUpdate();
//关闭资源
JdbcUtils.close(null,pst,connection);
}
}
2. JdbcTemplate(重点)
2.1 JdbcTemplate概念
JdbcTemplate
就是Spring对JDBC的封装,目的是使JDBC更加易于使用分类:
execute
:可以执行所有SQL语句,一般用于执行DDL语句。update
:用于执行INSERT 、UPDATE 、DELETE 等DML语句。queryXxx
:用于DQL数据查询语句。
- 优点:
- 不需要自行管理连接;
- 不需要自行设置参数;
- 查询时可直接返回对应的实体类。
2.2 JdbcTemplate的创建和使用
2.2.1 使用DRUID实现连接池
2.2.1.1 常用方法
- public JdbcTemplate(DataSource dataSource); 创建JdbcTemplate对象
- public void execute(final String sql); execute可以执行所有SQL语句,一般是执行DDL语句
2.2.1.2 使用步骤
- 创建DruidDataSource连接池;
- 导入JdbcTemplate的jar包;
- 创建
JdbcTemplate
对象,传入Druid 连接池; - 调用
execute
、update
、queryXxx
等方法。
public class Demo01 {
public static void main(String[] args) {
//创建的JdbcTemplate对象
JdbcTemplate jdbcTemplate = new JdbcTemplate(JdbcUtils.getDataSource());
// 创建表的SQL语句
String sql = "CREATE TABLE CAR(cid INT PRIMARY KEY AUTO_INCREMENT,cname VARCHAR(20),price DOUBLE);";
jdbcTemplate.execute(sql);
}
}
2.3 JdbcTemplate实现增删改
2.3.1 常用方法
- public void update(final String sql); update用于执行DML语句
2.3.2 使用步骤
创建JdbcTemplate对象
编写SQL语句
使用JdbcTemplate对象的
update
方法进行增删改
2.3.3 增删改示例案例
需求:使用JdbcTemplate实现增删改
import Utils.JdbcUtils;
import org.junit.jupiter.api.Test;
import org.springframework.jdbc.core.JdbcTemplate;
public class Notes02 {
//创建JdbcTemplate对象
JdbcTemplate jdbcTemplate = new JdbcTemplate(JdbcUtils.getDataSource());
/*创建表*/
@Test
public void createTable(){
//编写SQL语句
String sql1 = "create table if not EXISTS person(id int primary key auto_increment, name varchar(10),age int);";
jdbcTemplate.execute(sql1);
}
/*增加数据*/
@Test
public void add() {
String sql = "insert into person(name,age) values (?,?);";
jdbcTemplate.update(sql,"小明",18);
jdbcTemplate.update(sql,"小黄",20);
}
/*删除数据*/
@Test
public void delete() {
String sql = "DELETE FROM person WHERE id = ?;";
jdbcTemplate.update(sql,2);
}
/*更新数据*/
@Test
public void update() {
String sql = "UPDATE person SET age = ? WHERE name = ?;";
jdbcTemplate.update(sql,20,"小明");
}
}
2.4 JdbcTemplate实现查询
2.4.1 返回值为int和long
- public int queryForInt(String sql); 执行查询语句,返回一个int类型的值。
- public long queryForLong(String sql); 执行查询语句,返回一个long类型的值。
2.4.2 使用queryForObject查询单个字段/单个实体类对象
- public <T> T queryForObject(String sql, Class<T> requiredType); 执行查询语句,返回一个指定类型的数据。
比如返回类型指定为String:
String str = JdbcTemplate对象名.queryForObject(sql, String.class);
注意事项:
使用
queryForObject
查询时,如果没有结果会报错queryForObject
返回的可以是单个字段,也可以是单个实体类对象(使用BeanPropertyRowMapper类)
2.4.3 使用queryForMap查询单个对象,返回一个Map对象
- public Map<String, Object> queryForMap(String sql); 执行查询语句,将一条记录放到一个Map对象中。
注意事项:
1. 一个Map对象就存储一条记录,key存储的是列名,value存储的是该记录中该列对应的值
2. 返回的不是实体类对象,仅仅是一个Map对象
2.4.4 使用queryForList查询返回多个对象
- public List<Map<String, Object>> queryForList(String sql);
执行查询语句,返回一个List集合,List中存放的是Map类型的数据。
2.4.5 query使用自定义RowMapper实现一次性查询多个实体类对象(多用于多表查询)
- public <T> List<T> query(String sql, RowMapper<T> rowMapper);
执行查询语句,返回一个List集合,List中存放的是RowMapper指定类型的数据
RowMapper<T>接口的重写方法中的resultSet:jdbctemplate每得到一行数据,都会直接给了resultSet对象,而i为当前是第几行
获取RowMapper,可通过结果集获取相应的值赋予给指定类对象的参数(详见案例)
使用步骤
- 定义一个类
- 创建JdbcTemplate对象
- 编写查询的SQL语句
- 使用JdbcTemplate对象的query方法,并传入RowMapper匿名内部类
- 在匿名内部类中将结果集中的一行记录转成一个指定类型对象
2.4.6 query使用BeanPropertyRowMapper实现一次性查询多个实体类对象(只能单表查询)
- public <T> List<T> query(String sql, new BeanPropertyRowMapper<T> (Class<T> requiredType));
执行查询语句,返回一个List集合,List中存储的是实体类对象,其中BeanPropertyRowMapper类实现了RowMapper接口。
- public class BeanPropertyRowMapper<T> implements RowMapper<T>
BeanPropertyRowMapper类实现了RowMapper接口
2.4.7 示例代码
import Utils.JdbcUtils;
import org.junit.jupiter.api.Test;
import org.springframework.jdbc.core.BeanPropertyRowMapper;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.core.RowMapper;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.List;
import java.util.Map;
public class Notes03 {
JdbcTemplate jdbcTemplate = new JdbcTemplate(JdbcUtils.getDataSource());
/*
queryForObject查询单个字段
*/
@Test
public void test01() {
String sql = "SELECT COUNT(id) FROM student";
//使用JdbcTemplate对象调用queryForObject
int count = jdbcTemplate.queryForObject(sql, int.class);
System.out.println("总人数:" + count);
}
/*
queryForObject查询单个实体类对象
*/
@Test
public void test02() {
String sql = "SELECT * FROM student WHERE id = ?";
//使用JdbcTemplate对象调用queryForObject,传入BeanPropertyRowMapper对象
Student student = jdbcTemplate.queryForObject(sql, new BeanPropertyRowMapper<>(Student.class),3);
System.out.println("查询结果:" + student);
}
/*
queryForMap查询单个实体类对象,返回Map对象
*/
@Test
public void test03() {
String sql = "SELECT * FROM student WHERE id = ?";
//使用JdbcTemplate对象调用queryForMap
Map<String, Object> som = jdbcTemplate.queryForMap(sql,2);
System.out.println("查询结果:" + som);
}
/*
queryForList查询多个实体类对象,返回Map对象组成的List集合
*/
@Test
public void test04() {
String sql = "SELECT * FROM student";
//使用JdbcTemplate对象调用queryForList,返回的是List集合,里面存储Map
List<Map<String, Object>> mapList = jdbcTemplate.queryForList(sql);
System.out.println("查询结果:" + mapList);
}
/*
query查询多个实体类对象,使用自定义rowMapper
*/
@Test
public void test05() {
String sql = "SELECT * FROM student";
//使用JdbcTemplate对象调用query,使用匿名内部类定义rowMapper
List<Student> list = jdbcTemplate.query(sql, new RowMapper<Student>() {
/*
mapRow(ResultSet resultSet, int i)
resultSet : jdbctemplate 每得到一行数据,都会直接给了resultSet对象。
i:当前是第几行。
*/
@Override
public Student mapRow(ResultSet resultSet, int i) throws SQLException {
Student student = new Student();
student.setId(resultSet.getInt("id"));
student.setName(resultSet.getString("name"));
student.setAge(resultSet.getInt("age"));
return student;
}
});
System.out.println("查询结果:" + list);
}
/*
query查询多个实体类对象,返回list集合,里面存放实体类对象
*/
@Test
public void test06() {
String sql = "SELECT * FROM student";
List<Student> list = jdbcTemplate.query(sql,new BeanPropertyRowMapper<>(Student.class));
System.out.println("查询的学生:"+ list);
}
}
3. 数据库三层架构(重点)
3.1 分层的作用
- 解耦:降低层与层之间的耦合性;
- 可维护性:提高软件的可维护性,对现有功能进行修改和更新时不会影响原有的功能;
- 可扩展性:提高软件的可扩展性,添加新的功能时不影响到现有的功能;
- 可重用性:不同层之间进行功能调用时,相同的功能可重复使用。
也有缺点:
- 工作量增加,需要分包,编写很多类
- 代码复杂程度提升
3.2 分层的方式
- 按照包进行划分
- view表示层-service业务逻辑层-dao数据访问层
- 其他的包就不分层,如工具类,实体类,测试类
3.3 使用分层实现用户登录和注册功能
前提:数据库中必须要先把用户表创建出来才能执行
准备工作:先创建资源(用户类)
public class User {
private int id;
private String name;
private String password;
public User() {
}
public User(String name, String password) {
this.name = name;
this.password = password;
}
public User(int id, String name, String password) {
this.id = id;
this.name = name;
this.password = password;
}
@Override
public String toString() {
return "User{" +
"id=" + id +
", name='" + name + '\'' +
", password='" + password + '\'' +
'}';
}
/*此处省略getter&setter,实际是有的*/
}
- 然后是提供一个工具类,用来创建连接池,获取连接,关闭连接等操作
import com.alibaba.druid.pool.DruidDataSourceFactory;
import javax.sql.DataSource;
import java.io.InputStream;
import java.sql.*;
import java.util.Properties;
public class JdbcUtils {
private static DataSource dataSource ;
/* 配置文件的加载只需加载一次,使用静态代码块*/
static{
try {
Properties properties =new Properties();
InputStream inputStream = JdbcUtils.class.getResourceAsStream("/druid.properties");
properties.load(inputStream);
//创建druid的连接池
dataSource = DruidDataSourceFactory.createDataSource(properties);
} catch (Exception e) {
e.printStackTrace();
}
}
//获取连接池
public static DataSource getDataSource(){
return dataSource;
}
//获取连接
public static Connection getConnection() throws SQLException {
return dataSource.getConnection();
}
//关闭资源
public static void close(ResultSet rs , Statement st , Connection conn){
if(rs!=null){
try {
rs.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if(st!=null){
try {
st.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if(conn!=null){
try {
conn.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
}
- view层代码
package com.java.view;
import com.java.User.User;
import com.java.service.UserService;
import java.util.Scanner;
public class AppAdmin {
private static Scanner sc = new Scanner(System.in);
//创建service对象
private static UserService userService = new UserService();
public static void main(String[] args) {
System.out.println("请选择功能:1(注册) 2(登录)");
String option = sc.nextLine();
if ("1".equalsIgnoreCase(option)) {
reg();
} else if ("2".equalsIgnoreCase(option)) {
login();
} else {
System.out.println("你的选择有误");
}
}
/*
登录功能
*/
private static void login() {
System.out.println("请输入用户信息");
System.out.println("用户名:");
String name = sc.nextLine();
System.out.println("密码:");
String password = sc.nextLine();
//封装到User对象中发送
User user = new User(name,password);
if (userService.login(user)) {
System.out.println("欢迎" + user.getName() + "登录成功");
}else {
System.out.println("用户名或者密码有误!!!");
}
}
/*
注册功能
*/
private static void reg() {
String name = null;
while (true) {
System.out.println("请输入用户注册信息");
System.out.println("用户名:");
name = sc.nextLine();
//向service层发送name,如果返回的是false,说明用户名不存在,否则用户名已被注册
if (!userService.isExist(name)) {
//取反后判断是true,说明用户名不存在,可以注册,跳出循环
break;
} else {
System.out.println("用户名已注册!");
}
}
System.out.println("密码:");
String password = sc.nextLine();
//把数据封装到User对象
User user = new User(name,password);
//将数据传输给数据库
userService.saveUser(user);
System.out.println("您已注册成功");
}
}
- service层代码
package com.java.service;
import com.java.User.User;
import com.java.dao.UserDao;
public class UserService {
private UserDao userDao = new UserDao();
//检查用户名是否存在
public boolean isExist(String name) {
//根据用户名查询用户,如果返回null则不存在,返回有用户则存在用户
User user = userDao.findUserName(name);
return user != null;
/*
逻辑是向数据库发送查询信息,返回有用户,则往view层返回true;如果返回null,则往view层返回false
*/
}
//保存用户信息
public void saveUser(User user) {
userDao.addUser(user);
}
//登录功能
public boolean login(User user) {
User targetUser = userDao.login(user);
return targetUser != null;
/*
逻辑:向数据库发送注册信息,返回有用户,则向view层返回true;若返回null,则往view层返回false
*/
}
}
- dao层代码
package com.java.dao;
import Utils.JdbcUtils;
import com.java.User.User;
import org.springframework.dao.DataAccessException;
import org.springframework.jdbc.core.BeanPropertyRowMapper;
import org.springframework.jdbc.core.JdbcTemplate;
public class UserDao {
JdbcTemplate jdbcTemplate = new JdbcTemplate(JdbcUtils.getDataSource());
public User findUserName(String name) {
//如果根据用户名查询到用户,则返回用户,如果报错,则返回null
//注意queryForObject方法如果查询不到用户不会返回null,而是直接报错
try {
String sql = "SELECT * FROM user WHERE name = ?;";
User user = jdbcTemplate.queryForObject(sql, new BeanPropertyRowMapper<>(User.class), name);
return user;
} catch (DataAccessException e) {
return null;
}
}
public void addUser(User user) {
String sql = "insert into user(name,password) values (?,?);";
jdbcTemplate.update(sql,user.getName(),user.getPassword());
}
public User login(User user) {
//根据传入的user的信息查询用户,存在返回用户,不存在报错,此时catch后返回null
try {
String sql = "select * from user where name = ? and password = ?;";
User targetUser = jdbcTemplate.queryForObject(sql, new BeanPropertyRowMapper<>(User.class), user.getName(), user.getPassword());
return targetUser;
} catch (DataAccessException e) {
return null;
}
}
}