文章目录
JDBC
说到java操作数据库jdbc一定是饶不开的。Mybatis其实也就是对JDBC进行了封装
// 注册 JDBC 驱动
Class.forName("com.mysql.cj.jdbc.Driver");
// 打开连接
conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/spring-shiwu? characterEncoding=utf-8&serverTimezone=UTC", "root", "123456");
// 执行查询
stmt = conn.createStatement();
String sql = "SELECT id,user_name,real_name,password,age,d_id from t_user where id = 1"; ResultSet rs = stmt.executeQuery(sql);
// 获取结果集
while (rs.next()) {
Integer id = rs.getInt("id");
String userName = rs.getString("user_name");
String realName = rs.getString("real_name");
String password = rs.getString("password");
Integer did = rs.getInt("d_id");
user.setId(id);
user.setUserName(userName);
user.setRealName(realName);
user.setPassword(password);
user.setDId(did);
System.out.println(user);
}
- jdbc操作步骤
- Class.forName注册驱动
- 获取一个Connection对象
- 创建一个Statement对象
- execute()方法执行SQL语句,获取ResultSet结果集
- 通过ResultSet结果集给POJO的属性赋值
- 最后关闭相关的资源
这种我们需要自己来维护管理资源的连接,如果忘记了,就很可能造成数据库服务连接耗尽。具体业务的SQL语句直接在代码中写死耦合性增强。此外,每个连接都会经历这几个步骤,重复代码很多。
- 总结上面的操作的特点
- 代码重复
- 资源管理
- 结果集处理
- SQL耦合(即不灵活,每次都是重写sql语句,而不是一个相对来说万能一些的方法,可以动态的传入参数,然后动态的进行不同的功能)
JDBC优化1
先从代码重复和资源管理方面来优化,我们可以创建一个工具类来专门处理这个问题
public class DBUtils {
private static final String JDBC_URL = "jdbc:mysql://localhost:3306/mybatisdb?characterEncoding=utf-8&serverTimezone=UTC";
private static final String JDBC_NAME = "root";
private static final String JDBC_PASSWORD = "123456";
private static Connection conn;
/**
* 对外提供获取数据库连接的方法
*/
public static Connection getConnection() throws Exception {
if(conn == null){
try{
conn = DriverManager.getConnection(JDBC_URL,JDBC_NAME,JDBC_PASSWORD);
}catch (Exception e){
e.printStackTrace();
throw new Exception();
}
}
return conn;
}
/**
* 关闭资源
* @param conn
*/
public static void close(Connection conn ){
close(conn,null);
}
public static void close(Connection conn, Statement sts ){
close(conn,sts,null);
}
public static void close(Connection conn, Statement sts , ResultSet rs){
if(rs != null){
try {
rs.close();
}catch (Exception e){
e.printStackTrace();
}
}
if(sts != null){
try {
sts.close();
}catch (Exception e){
e.printStackTrace();
}
}
if(conn != null){
try {
conn.close();
}catch (Exception e){
e.printStackTrace();
}
}
}
}
这样JDBC代码就可以调用这个工具类的相应的代码,看上去就会好一些了,但还不够
JDBC优化2
我们可以针对DML(即增删改)操作的方法来优化,先解决SQL耦合的问题,在DBUtils中封装DML操作的方法
/**
* 执行数据库的DML操作
* @return
*/
public static Integer update(String sql,Object ... paramter) throws Exception{
conn = getConnection();
//动态的拼接参数
PreparedStatement ps = conn.prepareStatement(sql);
if(paramter != null && paramter.length > 0){
for (int i = 0; i < paramter.length; i++) {
//这个setObject方法是自带的api,就是封装参数的
ps.setObject(i+1,paramter[i]);
}
}
int i = ps.executeUpdate();
//调用的工具类方法
close(conn,ps);
return i;
}
这是JDBC代码简化如下
public void addUser(){
String sql = "INSERT INTO T_USER(user_name,real_name,password,age,d_id)values(?,?,?,?,?)";
try {
DBUtils.update(sql,"bobo","王五","111",22,1001);
} catch (Exception e) {
e.printStackTrace();
}
}
sql耦合的问题解决了,但还是没有解决ResultSet结果集的处理问题,所以我们还需要继续优化
JDBC优化3
要对结果集优化,即进行动态的赋值对象,不难想到要使用反射
public static <T> List<T> query(String sql, Class clazz, Object ... parameter) throws Exception{
//跟刚才的update方法基本一模一样
conn = getConnection();
PreparedStatement ps = conn.prepareStatement(sql);
if(parameter != null && parameter.length > 0){
for (int i = 0; i < parameter.length; i++) {
ps.setObject(i+1,parameter[i]);
}
}
ResultSet rs = ps.executeQuery();
// 获取对应的表结构的元数据
ResultSetMetaData metaData = ps.getMetaData();
List<T> list = new ArrayList<>();
while(rs.next()){
// 根据 字段名称获取对应的值 然后将数据要封装到对应的对象中
int columnCount = metaData.getColumnCount();
Object o = clazz.newInstance();
//数据库中的列位移是从1开始的
for (int i = 1; i < columnCount+1; i++) {
// 根据每列的名称获取对应的值
String columnName = metaData.getColumnName(i);
//通过数据的列拿到结果集的对应字段的值
Object columnValue = rs.getObject(columnName);
//利用反射进行对象的赋值
setFieldValueForColumn(o,columnName,columnValue);
}
list.add((T) o);
}
return list;
}
private static void setFieldValueForColumn(Object o, String columnName,Object columnValue) {
Class<?> clazz = o.getClass();
try {
// 根据字段获取属性
Field field = clazz.getDeclaredField(columnName);
// 私有属性放开权限
field.setAccessible(true);
field.set(o,columnValue);
field.setAccessible(false);
}catch (Exception e){
// 说明不存在 那就将 _ 转换为 驼峰命名法 user_name --> userName
if(columnName.contains("_")){
Pattern linePattern = Pattern.compile("_(\\w)");
columnName = columnName.toLowerCase();
Matcher matcher = linePattern.matcher(columnName);
StringBuffer sb = new StringBuffer();
while (matcher.find()) {
matcher.appendReplacement(sb, matcher.group(1).toUpperCase());
}
matcher.appendTail(sb);
// 再次调用复制操作
setFieldValueForColumn(o,sb.toString(),columnValue);
}
}
}
Apache DBUtils(效率很高)
DButils中提供了一个QueryRunner类,它对数据库的增删改查的方法进行了封装,获取QueryRunner的方式如下
druid.properties配置文件
druid.username=root
druid.password=admin
druid.url=jdbc:mysql://localhost:3306/spring-shiwu?characterEncoding=utf-8&serverTimezone=UTC
druid.minIdle=10
druid.maxActive=30
private static final String PROPERTY_PATH = "druid.properties";
private static DruidDataSource dataSource;
private static QueryRunner queryRunner;
public static void init() {
//这个api之前spring也用过
Properties properties = new Properties();
//加载配置文件
InputStream in = DruidUtils.class.getClassLoader().getResourceAsStream(PROPERTY_PATH);
try {
properties.load(in);
} catch (IOException e) {
e.printStackTrace();
}
//建立数据源
dataSource = new DruidDataSource();
dataSource.configFromPropety(properties);
// 使用数据源初始化 QueryRunner
queryRunner = new QueryRunner(dataSource);
}
QueryRunner中提供的方法解决了重复代码的问题,传入数据源解决了资源管理的问题。而对于ResultSet结果集的处理则是通过 ResultSetHandler 来处理(它是一个接口,提供了一些实现类)。我们也可以自己来实现该接口,或者用DBUtils中提供的默认的相关实现来解决,默认实现是ArrayHandler,handle方法把它转换成object数组
自己实现
public void queryUser() throws Exception{
DruidUtils.init();
QueryRunner queryRunner = DruidUtils.getQueryRunner();
String sql = "select * from t_user";
//ResultSet结果集的处理则是通过 ResultSetHandler 来处理。我们可以自己来实现该接口,这里是lambda表达式写法
//或者用DBUtils中提供的默认的相关实现来解决,默认实现是ArrayHandler,handle把它转换成object数组
List<User> list = queryRunner.query(sql, new ResultSetHandler<List<User>>() {
@Override
public List<User> handle(ResultSet rs) throws SQLException {
List<User> list = new ArrayList<>();
while(rs.next()){
User user = new User();
user.setId(rs.getInt("id"));
user.setUserName(rs.getString("user_name"));
user.setRealName(rs.getString("real_name"));
user.setPassword(rs.getString("password"));
list.add(user);
}
return list;
}
});
for (User user : list) {
System.out.println(user);
}
}
通过ResultHandle的实现类处理查询
public void queryUserUseBeanListHandle() throws Exception{
DruidUtils.init();
QueryRunner queryRunner = DruidUtils.getQueryRunner();
String sql = "select * from t_user";
// 不会自动帮助我们实现驼峰命名的转换
List<User> list = queryRunner.query(sql, new BeanListHandler<User>(User.class));
for (User user : list) {
System.out.println(user);
}
}
Spring JDBC(效率很高)
在Spring中提供了一个模板方法JdbcTemplate,里面封装了各种各样的 execute,query和update方法。JdbcTemplate这个类是JDBC的核心包的中心类,简化了JDBC的操作,可以避免常见的异常,它封装了
JDBC的核心流程,应用只要提供SQL语句,提取结果集就可以了,它是线程安全的。
初始配置类
@Configuration
@ComponentScan
public class SpringConfig {
@Bean
public DataSource dataSource(){
DruidDataSource dataSource = new DruidDataSource();
dataSource.setUsername("root");
dataSource.setPassword("admin");
dataSource.setUrl("jdbc:mysql://localhost:3306/spring-shiwu?characterEncoding=utf-8&serverTimezone=UTC");
return dataSource;
}
@Bean
public JdbcTemplate jdbcTemplate(DataSource dataSource){
JdbcTemplate template = new JdbcTemplate();
template.setDataSource(dataSource);
return template;
}
}
CRUD操作
@Repository
public class UserDao {
@Autowired
private JdbcTemplate template;
public void addUser(){
int count = template.update("insert into t_user(user_name,real_name)values(?,?)","bobo","波波老师");
System.out.println("count = " + count);
}
public void query1(){
String sql = "select * from t_user";
List<User> list = template.query(sql, new RowMapper<User>() {
@Override
public User mapRow(ResultSet rs, int rowNum) throws SQLException {
User user = new User();
user.setId(rs.getInt("id"));
user.setUserName(rs.getString("user_name"));
user.setRealName(rs.getString("real_name"));
return user;
}
});
for (User user : list) {
System.out.println(user);
}
}
public void query2(){
String sql = "select * from t_user";
List<User> list = template.query(sql, new BeanPropertyRowMapper<>(User.class));
for (User user : list) {
System.out.println(user);
}
}
}
SpringJdbc的查询结果集映射跟Apache DBUtils同理的,代码基本完全一样
Hibernate
Apache DBUtils和SpringJdbcTemplate虽然简化了数据库的操作,但是本身提供的功能还是比较简单的(缺少缓存,事务管理等),所以我们在实际开发中往往并没有直接使用上述技术,而是用到了Hibernate(现在也很少用)和MyBatis等这些专业的ORM持久层框架。
什么是ORM
ORM( Object Relational Mapping) ,也就是对象与关系的映射,对象是程序里面的对象,关系是它
与数据库里面的数据的关系,也就是说,ORM框架帮助我们解决的问题是程序对象和关系型数据库的相
互映射的问题
Hebernate的使用
引入maven依赖
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.11</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
实体类配置文件
<hibernate-mapping>
<class name="com.gupaoedu.vip.model.User" table="t_user">
<id name="id" />
<property name="userName" column="user_name"></property>
<property name="realName" column="real_name"></property>
</class>
</hibernate-mapping>
Hibernate的配置文件
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE hibernate-configuration PUBLIC
"-//Hibernate/Hibernate Configuration DTD 3.0//EN"
"http://www.hibernate.org/dtd/hibernate-configuration-3.0.dtd">
<hibernate-configuration>
<session-factory>
<property name="hibernate.connection.driver_class">
com.mysql.cj.jdbc.Driver
</property>
<property name="hibernate.connection.url">
jdbc:mysql://localhost:3306/mybatisdb?characterEncoding=utf8&serverTimezone=UTC
</property>
<property name="hibernate.connection.username">root</property>
<property name="hibernate.connection.password">123456</property>
<property name="hibernate.dialect">
org.hibernate.dialect.MySQLDialect
</property>
<property name="hibernate.show_sql">true</property>
<property name="hibernate.format_sql">true</property>
<property name="hibernate.hbm2ddl.auto">update</property>
<mapping resource="User.hbm.xml"/>
</session-factory>
</hibernate-configuration>
CRUD 操作
public class HibernateTest {
/**
* Hibernate操作案例演示
* @param args
*/
public static void main(String[] args) {
Configuration configuration = new Configuration();
// 默认使用hibernate.cfg.xml
configuration.configure();
// 创建Session工厂
SessionFactory factory = configuration.buildSessionFactory();
// 创建Session
Session session = factory.openSession();
// 获取事务对象
Transaction transaction = session.getTransaction();
// 开启事务
transaction.begin();
// 把对象添加到数据库中
User user = new User();
user.setId(668);
user.setUserName("hibernate-1");
user.setRealName("持久层框架");
session.save(user);
transaction.commit();
session.close();
}
}
映射文件也可通过注解方式
Data
@Entity
@Table(name = "t_user")
public class User {
@Id
@Column(name = "id")
private Integer id;
@Column(name = "user_name")
private String userName;
@Column(name = "real_name")
private String realName;
@Column(name = "password")
private String password;
@Column(name = "age")
private Integer age;
@Column(name = "i_id")
private Integer dId;
}
在Spring中给我们提供的JPA对持久层框架做了统一的封装,而且本质上就是基于HibernateJPA来实现
的,所以我们在使用的时候也可以通过SpringDataJPA的API来操作
dao的接口只需要继承JpaRepository接口(类似于mybatis-plus的那个继承)即可
public interface IUserDao extends JpaRepository<User,Integer> {
}
@Service
public class UserServiceImpl implements IUserService {
@Autowired
private IUserDao dao;
@Override
public List<User> query() {
return dao.findAll();
}
@Override
public User save(User user) {
return dao.save(user);
}
}
Hibernate优缺点
- 优点
- 根据数据库方言自定生成SQL,移植性好
- 自动管理连接资源
- 实现了对象和关系型数据的完全映射,操作对象就想操作数据库记录一样
- 提供了缓存机制
- 缺点
- 比如API中的get(),update()和save()方法,操作的实际上是所有的字段,没有办法指定部分字段,
换句话说就是不够灵活(因为它是全自动的) - 自定生成SQL的方式,如果要基于SQL去做一些优化的话,也是非常困难的。
- 不支持动态SQL,比如分表中的表名,条件,参数变化等,无法根据条件自动生成SQL
因此我们需要一个更为灵活的框架
- 比如API中的get(),update()和save()方法,操作的实际上是所有的字段,没有办法指定部分字段,