spring为我们提供了一个封装好了的数据库操作API,我们可以很方便的使用他们来完成数据库操作
以下是spring为我们做的(来自官方文档)
Action | Spring | You |
---|---|---|
Define connection parameters. | X | |
Open the connection. | X | |
Specify the SQL statement. | X | |
Declare parameters and provide parameter values | X | |
Prepare and execute the statement. | X | |
Set up the loop to iterate through the results (if any). | X | |
Do the work for each iteration. | X | |
Process any exception. | X | |
Handle transactions. | X | |
Close the connection, the statement, and the resultset. | X |
Spring Framework负责处理所有可能使JDBC成为繁琐的API的低级细节。
连接数据库
如何连接数据库?
- DataSource
DataSource是JDBC规范的一部分,是一个通用的连接工厂。它允许容器或框架从应用程序代码中隐藏连接池和事务管理问题。
- DataSourceUtils
该DataSourceUtils班是一个方便且功能强大的辅助类,提供 static方法来获取从JNDI和连接,如果有必要密切的联系。例如,它支持线程绑定连接DataSourceTransactionManager。
- SmartDataSource
该SmartDataSource接口应该由能提供一个关系数据库的连接类实现。它扩展了DataSource接口,让使用它的类查询是否应该在给定操作后关闭连接。当您知道需要重用连接时,此用法很有效。
- AbstractDataSource
AbstractDataSource是DataSource 实现的基类。它实现了所有DataSource实现共有的代码。如果编写自己的DataSource 实现,则应该扩展AbstractDataSource类。
- SingleConnectionDataSource
的SingleConnectionDataSource类是的一个实现SmartDataSource ,它包装单个接口Connection的是在每次使用后不关闭。这不是多线程的。
SingleConnectionDataSource主要是一个测试类。例如,它可以在简单的JNDI环境中轻松测试应用程序服务器外部的代码。与此相反 DriverManagerDataSource,它始终重用相同的连接,避免过度创建物理连接。
- DriverManagerDataSource
该DriverManagerDataSource班是标准的实现DataSource 是通过配置bean的属性的纯JDBC驱动程序,并返回一个新的接口 Connection每次。
此实现对于Java EE容器外部的测试和独立环境非常有用,可以作为DataSourceSpring IoC容器中的bean ,也可以与简单的JNDI环境结合使用。
- TransactionAwareDataSourceProxy
TransactionAwareDataSourceProxy是目标的代理DataSource。代理包装该目标DataSource以增加对Spring管理的事务的认知。在这方面,它类似于DataSourceJava EE服务器提供的事务性JNDI 。
- DataSourceTransactionManager
该DataSourceTransactionManager类是PlatformTransactionManager 为单JDBC数据源的实现。它将JDBC连接从指定的数据源绑定到当前正在执行的线程,可能允许每个数据源一个线程连接。
下面是如何连接的示例代码
使用的是DriverManagerDataSource
DriverManagerDataSource dataSource = new DriverManagerDataSource();
dataSource.setDriverClassName("com.mysql.jdbc.Driver");
dataSource.setUrl("jdbc:mysql://localhost:3306/school");
dataSource.setUsername("root");
dataSource.setPassword("123456");
另一种,基于xml的配置
<bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<property name="driverClassName" value="${jdbc.driverClassName}"/>
<property name="url" value="${jdbc.url}"/>
<property name="username" value="${jdbc.username}"/>
<property name="password" value="${jdbc.password}"/>
</bean>
<!-- spring 提供的一种加载 propreties 配置文件的方式 -->
<context:property-placeholder location="classpath:jdbc.properites"/>
<!-- 或者location="src/jdbc.properites" -->
在src下,建立jdbc.properites配置文件
jdbc.driver = com.mysql.jdbc.Driver
jdbc.url = jdbc:mysql://localhost:3306/school
jdbc.username = root
jdbc.password = 123456
一般是使用JDBCT.properites来配置,写在java代码里的方式修改时麻烦,易出错。
DriverManagerDataSource
仅将该类用于测试目的,因为它不提供池,并且在进行多个连接请求时性能很差
下面是DBCP的配置
<bean id="dataSource"
class="org.apache.commons.dbcp2.BasicDataSource"
destroy-method="close">
<property name="driverClassName" value="${jdbc.driver}" />
<property name="url" value="${jdbc.url}" />
<property name="username" value="${jdbc.username}" />
<property name="password" value="${jdbc.password}" />
</bean>
可以看到,在Class里是org.apache.commons.dbcp2.BasicDataSource
我们要导入jdbc驱动包:mysql-connector-java-5.1.15-bin.jar
使用连接池,需要加入连接池的jar包,这里使用的是
- commons-dbcp2-2.2.0.jar
- commons-pool2-2.6.2.jar
这里要注意,使用的版本,在此版本里,org.apache.commons.dbcp2.BasicDataSource
,类路径是dbcp2
下面来看一个最精简的完整示例
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
https://www.springframework.org/schema/context/spring-context.xsd">
<bean id="studentDao" class="com.dao.StudentDao">
<property name="dataSource" ref="dataSource" />
</bean>
<bean id="dataSource"
class="org.apache.commons.dbcp2.BasicDataSource"
destroy-method="close">
<property name="driverClassName" value="${jdbc.driver}" />
<property name="url" value="${jdbc.url}" />
<property name="username" value="${jdbc.username}" />
<property name="password" value="${jdbc.password}" />
</bean>
<context:property-placeholder
location="classpath:jdbc.properites" system-properties-mode="FALLBACK" />
</beans>
在配置文件里,把DataSource,注入到studentDao中
接下来,我们看StudentDao类的具体,内容
package com.dao;
import javax.sql.DataSource;
import org.springframework.jdbc.core.JdbcTemplate;
import com.domain.Student;
public class StudentDao {
private JdbcTemplate jdbcTemplate;
public void setDataSource(DataSource dataSource) {
jdbcTemplate = new JdbcTemplate(dataSource);
}
jdbcTemplate,是spring为我们提供的数据库操作的对象,我们可以直接执行sql语句,例如:
/**
* 执行sql语句,创建一个教师表
*
*/
public void createTableTeacher() {
String sql = "create table teacher (id integer,name varcher(20) )";
jdbcTemplate.execute(sql);
}
我们只需要提供要执行的sql语句,jdbcTemplate就会为我们完成 得到连接,执行,得到结果,关闭连接,对结果封装 然后返回,具体参考官方文档(spring-framework-reference/data-access.html#jdbc),的表格,本文的开头也有列出
数据库CRUD示例
练习数据库表
CREATE TABLE `student` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`sno` varchar(10) NOT NULL,
`name` varchar(10) NOT NULL,
`sex` varchar(2) NOT NULL,
`age` int(4) NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=5 DEFAULT CHARSET=utf8;
对应的实体类
package com.domain;
public class Student {
private int id;
private String sno;
private String name;
private String sex;
private int age;
//省略set get toString方法
//...
}
连接数据库配置文件
jdbc.driver = com.mysql.jdbc.Driver
jdbc.url = jdbc:mysql://localhost:3306/school?useUnicode=true&characterEncoding=UTF-8
jdbc.username = root
jdbc.password = 123456
?useUnicode=true&characterEncoding=UTF-8
显式指定数据库编码,防止乱码
StudentDao.java 数据库操作函数
package com.dao;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.List;
import java.util.Map;
import javax.sql.DataSource;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.core.RowMapper;
import com.domain.Student;
public class StudentDao {
private JdbcTemplate jdbcTemplate;
public void setDataSource(DataSource dataSource) {
jdbcTemplate = new JdbcTemplate(dataSource);
}
/**
* 执行sql语句,创建一个教师表
*
*/
public void createTableTeacher() {
String sql = "create table teacher (id integer,name varcher(20) )";
jdbcTemplate.execute(sql);
//直接执行sql语句,不需要返回值
}
/**
* 查询所有学生
* @return List<Student>学生列表
*/
public List<Student> listAllStudent(){
String sql = "select * from student";
return jdbcTemplate.query(sql, new StudentMap());
//传入 要执行的sql语句,用StudentMap告诉spring怎么封装返回的对象
}
/**
* 查询所有学生2
*/
public List<Map<String,Object>> listAllStudent2(){
String sql = "select * from student";
return jdbcTemplate.queryForList(sql);
/**
* 类似于 [{id=1,sno=001,name=li,....}{id=2,sno=002,name=zhang,...}]
* 多个map集合
*/
}
/**
* 根据id 查找学生
* @return Student 学生
*/
public Student getStudentById(int id) {
String sql = "select * from student where id=?";
Student s = jdbcTemplate.queryForObject(sql,new Integer[] {id},new StudentMap());
//传入 要执行的sql语句,sql语句参数id(这里是一个数组),用StudentMap告诉spring怎么封装返回的对象
return s;
}
/**
* 查询所有学生数量
* @return int 学生数量
*/
public int getAllStudentCount() {
String sql = "select count(*) from student";
return jdbcTemplate.queryForObject(sql, Integer.class);
//传入 要执行的sql语句,和要求返回的类型(Integer)
}
/**
* 根据学生id查询学生的姓名
* @param
* @return 学生姓名
*/
public String getStudentNameById(int id) {
String sql = "select name from student where id=?";
return jdbcTemplate.queryForObject(sql, String.class,id);
//传入 要执行的sql语句,和要求返回的类型(String),查询参数
}
/**
* 查询所有学生的姓名
* @return 学生姓名的String
*/
public List<String> getAllStudentName() {
return jdbcTemplate.queryForList("select name from student", String.class);
}
/**
* 根据id来修改学生姓名
* @param id
* @param name
* @return 受影响行数
*/
public int updateStudentNameById(int id,String name) {
String sql = "update student set name=? where id=?";
return jdbcTemplate.update(sql, name,id);
}
/**
* 插入一个学生
* @param s
* @return 受影响行数
*/
public int insertStudent(Student s) {
String sql = "insert into student (sno,name,sex,age) values (?,?,?,?)";
return jdbcTemplate.update(sql, s.getSno(),s.getName(),s.getSex(),s.getAge());
}
/**
* 根据 id 删除学生表中的对应记录
* @param id
* @return
*/
public int deleteStudentByid(int id) {
String sql = "delete from student where id=?";
return jdbcTemplate.update(sql, id);
//传入执行sql语句,和参数,返回受影响的行数
}
/**
* 封装了查询对象
*/
static class StudentMap implements RowMapper<Student>{
public Student mapRow(ResultSet rs, int rowNum) throws SQLException {
Student s = new Student();
s.setId(rs.getInt("id"));
s.setSno(rs.getString("sno"));
s.setName(rs.getString("name"));
s.setSex(rs.getString("sex"));
s.setAge(rs.getInt("age"));
return s;
}
}
}
StudentMap实现了RowMapper接口,这个接口是spring为我们提供的,专门封装查询对象的。
在getStudentById函数内(69行),Student s = jdbcTemplate.queryForObject(sql,new Integer[] {id},new StudentMap());
,将StudentMap对象传入,告诉spring如何做,它就能为我们返回一个封装好的对象,当我们有大量类似查询时,可以很方便的完成目标
Beans.xml配置文件
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
https://www.springframework.org/schema/context/spring-context.xsd">
<bean id="studentDao" class="com.dao.StudentDao">
<property name="dataSource" ref="dataSource" />
</bean>
<bean id="dataSource"
class="org.apache.commons.dbcp2.BasicDataSource"
destroy-method="close">
<property name="driverClassName" value="${jdbc.driver}" />
<property name="url" value="${jdbc.url}" />
<property name="username" value="${jdbc.username}" />
<property name="password" value="${jdbc.password}" />
</bean>
<context:property-placeholder
location="classpath:jdbc.properites" system-properties-mode="FALLBACK" />
<!-- 或者location="src/jdbc.properites" -->
</beans>
测试TestStudentDao
package com.test;
import org.junit.Before;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.FileSystemXmlApplicationContext;
import com.dao.StudentDao;
import com.domain.Student;
public class TestStudentDao {
StudentDao studentDao;
@Before
public void init() {
ApplicationContext context = new FileSystemXmlApplicationContext("src/Beans.xml");
studentDao = (StudentDao) context.getBean("studentDao");
}
@Test
public void TestQuery() {
System.out.println("得到所有学生数量:");
System.out.println(studentDao.getAllStudentCount());
System.out.println("得到所有学生的名字");
System.out.println(studentDao.getAllStudentName());
System.out.println("得到id为1学生的所有信息");
System.out.println(studentDao.getStudentById(1));
System.out.println("得到id为1的学生姓名");
System.out.println(studentDao.getStudentNameById(1));
System.out.println("得到所有学生");
System.out.println(studentDao.listAllStudent());
System.out.println("另一种方式");
System.out.println(studentDao.listAllStudent2());
}
@Test
public void TestAdd() {
System.out.println("增加一个学生");
Student s = new Student();
s.setSno("006");
s.setName("李");
s.setSex("男");
s.setAge(19);
studentDao.insertStudent(s);
}
@Test
public void TestUpdate() {
System.out.println("修改id为1的学生名字为:莉莉");
studentDao.updateStudentNameById(1, "莉莉");
}
@Test
public void TestDelete() {
System.out.println("删除id为2的学生");
System.out.println(studentDao.getStudentNameById(2) + "要被删除");
studentDao.deleteStudentByid(2);
}
}
说明:
以上的示例,只是spring为我们提供的一小部分API,详情参见官方文档
其他
NamedParameterJdbcTemplate
如果想要让代码更容易读,可以这样写
// some JDBC-backed DAO class...
private NamedParameterJdbcTemplate namedParameterJdbcTemplate;
public void setDataSource(DataSource dataSource) {
this.namedParameterJdbcTemplate = new NamedParameterJdbcTemplate(dataSource);
}
public int countOfActors(Actor exampleActor) {
String sql = "select count(*) from T_ACTOR where first_name = :firstName and last_name = :lastName";
//初始化一个解析 exampleActor所属类型(Actor),的 SqlParameterSource对象
SqlParameterSource namedParameters = new BeanPropertySqlParameterSource(exampleActor);
return this.namedParameterJdbcTemplate.queryForObject(sql, namedParameters, Integer.class);
//传入 要执行的sql,解析方式,要求返回的类型
}
这里使用了:firstName :last_name
来做占位符,比单纯使用”?“更加清晰
- 使用SimpleJdbc类简化JDBC操作
public class JdbcActorDao implements ActorDao {
private JdbcTemplate jdbcTemplate;
private SimpleJdbcInsert insertActor;
public void setDataSource(DataSource dataSource) {
this.jdbcTemplate = new JdbcTemplate(dataSource);
this.insertActor = new SimpleJdbcInsert(dataSource).withTableName("t_actor");
}
public void add(Actor actor) {
Map<String, Object> parameters = new HashMap<String, Object>(3);
parameters.put("id", actor.getId());
parameters.put("first_name", actor.getFirstName());
parameters.put("last_name", actor.getLastName());
insertActor.execute(parameters);
}
}
- 批处理操作
public class JdbcActorDao implements ActorDao {
private JdbcTemplate jdbcTemplate;
public void setDataSource(DataSource dataSource) {
this.jdbcTemplate = new JdbcTemplate(dataSource);
}
public int[] batchUpdate(final List<Actor> actors) {
return this.jdbcTemplate.batchUpdate(
"update t_actor set first_name = ?, last_name = ? where id = ?",
new BatchPreparedStatementSetter() {
public void setValues(PreparedStatement ps, int i) throws SQLException {
ps.setString(1, actors.get(i).getFirstName());
ps.setString(2, actors.get(i).getLastName());
ps.setLong(3, actors.get(i).getId().longValue());
}
public int getBatchSize() {
return actors.size();
}
});
}
// ... additional methods
}
- 嵌入式数据库支持
…等等,具体看官方文档
坑
关于加载propreties配置文件的一个小坑
<!-- 这是改完之后的正确写法 -->
<bean id="dataSource"
class="org.apache.commons.dbcp2.BasicDataSource" destroy-method="close">
<!-- <bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource"> -->
<property name="driverClassName" value="${jdbc.driver}"/>
<property name="url" value="${jdbc.url}"/>
<property name="username" value="${jdbc.username}"/>
<property name="password" value="${jdbc.password}"/>
</bean>
<!-- 加载配置 -->
<context:property-placeholder location="classpath:jdbc.properites"/>
使用spring提供的类加载 jdbc.properites 时,发现<property name="username" value="${username}"
里面的${username}
取值不对,报错 说Adminstor @ 'localhost'
密码错误。
原因是,这里使用了EL表达式,${username},代表的是,当前系统用户名(我的是win7-》取到的是Adminstor)
解决方式是
- 设置属性
<context:property-placeholder location=“classpath:jdbc.properites” system-properties-mode=“FALLBACK”/>
FALLBACK — 默认值,不存在时覆盖
NEVER — 不覆盖
- 避开
当然,最好是不要这么写,给属性加一个前缀避开就好
<property name="username" value="${jdbc.username}"/>
数据库连接要指定编码,否则会出现乱码
参考链接
https://www.w3cschool.cn/wkspring/iuck1mma.html
https://blog.csdn.net/fdk2zhang/article/details/83039416
https://blog.csdn.net/lxh123456789asd/article/details/81042826
官方文档