Spring+JDBC组合开发简介
在企业级的应用中会频繁跟数据库打交道,Spring在JDBC基础上做了封装,在数据库连接获取方面引入的数据库连接池,提供了数据库操作模板类JdbcTemplate类,极大简化的数据库操作API,提高数据库操作的运行性能,同时结合Spring的AOP特性提供了一套简便的数据库事务管理机制,可以通过注解对事务进行管理。主要解决事务的一致性。
编程式事务和声明式事务的区别
编程式事务:能够精确到语句上,作用范围小。
声明时事务:声明式事务作用在方法上,作用范围大,精确小。
所需要的依赖
<dependencies>
<!-- Spring核心包,Spring的框架包 -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>5.3.5</version>
</dependency>
<!-- AOP切面包,用来拦截方法和注入通知 -->
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.9.6</version>
</dependency>
<!-- 数据库连接池,阿里的数据库连接池,默认有十个数据库连接对象 -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.2.5</version>
</dependency>
<!--Oracle JDBC 驱动包(将自己的jdbc驱动导入maven仓库)-->
<dependency>
<groupId>com.oracle</groupId>
<artifactId>ojdbc14</artifactId>
<version>1.0</version>
</dependency>
<!--JDBC、Tx、关系对象映射包-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-orm</artifactId>
<version>5.3.5</version>
</dependency>
</dependencies>
将自带的jar包导入maven仓库
在命令行输入:
mvn install:install-file -Dfile="E:\jsd1906\03_JDBC\ojdbc14.jar" -DgroupId=com.oracle -DartifactId=ojdbc14 -Dversion=1.0 -Dpackaging=jar
-Dfile:jar所在的文件路径
-DgroupId:引入坐标的groupId名称
-DartifactId:引入坐标的artifactId名称
-Dversion:引入坐标的version名称
-Dpackaging:生成文件类型
数据源的配置
properties文件
在类路径下创建文件db.properties,在文件中加入数据库连接信息及连接池配置信息,为spring容器创建数据库连接池提供信息,配置文件内容如下:
#数据库驱动
driverClassName=oracle.jdbc.driver.OracleDriver
#连接Url
url=jdbc:oracle:thin:@127.0.0.1:1521:XE
#用户名
username=test
#密码
password=test
#连接池启动时初始连接数
initialSize=2
#最大活动连接数
maxActive=4
# 最大空闲值.当经过一个高峰后, 可以将已经用不到的连接慢慢释放一部分,减少到maxIdle为止
maxIdle=3
#最小空闲值.当空闲的连接数少于阀值时,连接池就会预申请一些连接,以免洪峰来时来不及申请
minIdle=2
xml文件
<!-- 加载jdbc.properties配置文件-->
<context:property-placeholder location="classpath:db.properties"/>
<!-- 配置数据源需要commons-dbcp.jar和commons-pool.jar- -->
<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">
<property name="driverClassName" value="${driverClassName}"/>
<property name="url" value="${url}"/>
<property name="username" value="${username}"/>
<property name="password" value="${password}"/>
<!-- 连接池启动时的初始值 -->
<property name="initialSize" value="${initialSize}"/>
<!-- 连接池的最大值 -->
<property name="maxActive" value="${maxActive}"/>
<!-- 最大空闲值.当经过一个高峰时间后,连接池可以慢慢将已经用不到的连接慢慢释放一部分,一直减少到maxIdle为止 -->
<property name="maxIdle" value="${maxIdle}"/>
<!-- 最小空闲值.当空闲的连接数少于阀值时,连接池就会预申请去一些连接,以免洪峰来时来不及申请 -->
<property name="minIdle" value="${minIdle}"/>
</bean>
Spring+JDBC组合开发-基于XML方式配置事务
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:tx="http://www.springframework.org/schema/tx"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx.xsd">
<!--包扫描-->
<context:component-scan base-package="com.entor.jdbc"/>
<!--
配置数据源
负责初始化数据库连接池
-->
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
<property name="driverClassName" value="oracle.jdbc.driver.OracleDriver"/>
<property name="url" value="jdbc:oracle:thin:@localhost:1521:XE"/>
<property name="username" value="jsd2101"/>
<property name="password" value="jsd2101"/>
</bean>
<!--
配置Spring数据源事务管理器
用来管理数据库连接池内的事务的提交和回滚
-->
<bean id="dataSourceTransactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"/>
</bean>
<!--
配置事务传播特性,设置目标对象的那些方法中使用事务管理
设置事务管理者
方法名以add,save,del,update,modify,remove开头的需要事务管理。
除此以外的不需要事务管理,只读操作。
不需要前置通知、后置通知等,事务默认封装了一系列的通知在内,若要自定义通知可以另外定义切面注入。
-->
<tx:advice id="advice" transaction-manager="dataSourceTransactionManager">
<tx:attributes>
<tx:method name="save*" propagation="REQUIRED"/>
<tx:method name="add*" propagation="REQUIRED"/>
<!--回滚原则 默认发生运行异常时回滚 自定义时,rollback-for优先-->
<tx:method name="del*" propagation="REQUIRED" rollback-for="Exception" no-rollback-for="RunTimeException"/>
<tx:method name="update*" propagation="REQUIRED"/>
<tx:method name="modify*" propagation="REQUIRED"/>
<tx:method name="remove*" propagation="REQUIRED"/>
<tx:method name="*" propagation="NOT_SUPPORTED" read-only="true"/>
</tx:attributes>
</tx:advice>
<!--配置aop管理切入点和通知-->
<aop:config>
<!--定义切面,匹配所有的连接点方法-->
<aop:pointcut id="pointcut" expression="execution(* com.entor.jdbc.service.impl.*.*(..))"/>
<!--把切面和通知关联上-->
<aop:advisor advice-ref="advice" pointcut-ref="pointcut"/>
</aop:config>
<!--设置数据库操作对象-->
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<property name="dataSource" ref="dataSource"/>
</bean>
</beans>
REQUIRED:业务方法需要在一个事务中运行。如果方法运行时,已经处在一个事务中,那么加入到该事务,否则为自己创建一个新的事务(开启事务的方法绝大部分使用这种类型事务)。
NOT_SUPPORTED:声明方法不需要事务。如果方法没有关联到一个事务,容器不会为它开启事务。如果方法在一个事务中被调用,该事务会被挂起,在方法调用结束后,原先的事务便会恢复执行。
REQUIRESNEW:属性表明不管是否存在事务,业务方法总会为自己发起一个新的事务。如果方法已经运行在一个事务中,则原有事务会被挂起,新的事务会被创建,直到方法执行结束,新事务才算结束,原先的事务才会恢复执行。
MANDATORY:该属性指定业务方法只能在一个已经存在的事务中执行,业务方法不能发起自己的事务。如果业务方法在没有事务的环境下调用,容器就会抛出例外。
SUPPORTS:这一事务属性表明,如果业务方法在某个事务范围内被调用,则方法成为该事务的一部分。如果业务方法在事务范围外被调用,则方法在没有事务的环境下执行。
Never:指定业务方法绝对不能在事务范围内执行。如果业务方法在某个事务中执行,容器会抛出例外,只有业务方法没有关联到任何事务,才能正常执行。
NESTED:如果一个活动的事务存在,则运行在一个嵌套的事务中. 如果没有活动事务, 则按REQUIRED属性执行.它使用了一个单独的事务, 这个事务拥有多个可以回滚的保存点。内部事务的回滚不会对外部事务造成影响。它只对DataSourceTransactionManager事务管理器起效
使用
Dao层
@Repository
public class StudentDaoImpl implements StudentDao {
@Autowired
private JdbcTemplate jdbcTemplate;
//新增
public void add(Student student) {
String sql = "insert into student(id,name,username,password,sex,age,birthday,create_time)values (seq_stu.nextval,?,?,?,?,?,?,sysdate)";
Object[] args = {student.getName(), student.getUsername(), student.getPassword(), student.getSex(), student.getAge(), student.getBirthday()};
int i = jdbcTemplate.update(sql, args);
System.out.println("更新了" + i + "条数据");
}
//批量新增
public void addMore(List<Student> list) {
String sql = "insert into student(id,name,username,password,sex,age,birthday,create_time)values (seq_stu.nextval,?,?,?,?,?,?,sysdate)";
List<Object[]> batchArgs = new ArrayList<Object[]>();
for (Student student : list) {
Object[] args = {student.getName(), student.getUsername(), student.getPassword(), student.getSex(), student.getAge(), student.getBirthday()};
batchArgs.add(args);
}
int[] ints = jdbcTemplate.batchUpdate(sql, batchArgs);
}
//删除
public void deleteById(Integer id) {
int i = jdbcTemplate.update("delete student where id = " + id);
System.out.println("删除了" + i + "条数据");
}
//批量删除
public void deleteByIds(String ids) {
int i = jdbcTemplate.update("delete student where id in " + ids);
System.out.println("删除了" + i + "条数据");
}
public void update(Student student) {
String sql = "update student set name=?,username=?,password=?,sex=?,age=?,birthday=? where id=?";
Object[] args = {student.getName(), student.getUsername(), student.getPassword(), student.getSex(), student.getAge(), student.getBirthday(), student.getId()};
int i = jdbcTemplate.update(sql, args);
System.out.println("更新了" + i + "条数据");
}
//查找
public Student queryById(Integer id) {
return jdbcTemplate.queryForObject("select * from student where id=" + id, (rs, i) -> {
Student student = new Student();
student.setId(rs.getInt("id"));
student.setName(rs.getString("name"));
student.setUsername(rs.getString("username"));
student.setPassword(rs.getString("password"));
student.setSex(rs.getInt("sex"));
student.setAge(rs.getInt("age"));
student.setBirthday(rs.getDate("birthday"));
student.setCreateTime(rs.getTimestamp("create_Time"));
return student;
});
}
//分页查找
public List<Student> queryByPage(Integer page, Integer size) {
Object[] args = {page * size, (page - 1) * size};
return jdbcTemplate.query("select * from (select s.*,rownum r from (select * from student)s where rownum<=?)ss where ss.r>?", (rs, i) -> {
Student student = new Student();
student.setId(rs.getInt("id"));
student.setName(rs.getString("name"));
student.setUsername(rs.getString("username"));
student.setPassword(rs.getString("password"));
student.setSex(rs.getInt("sex"));
student.setAge(rs.getInt("age"));
student.setBirthday(rs.getDate("birthday"));
student.setCreateTime(rs.getTimestamp("create_Time"));
return student;
}, args);
}
//总记录数
public int getTotal() {
return jdbcTemplate.queryForObject("select count(*) from student", int.class);
}
//登录验证
public Student login(String username, String password) {
Object[] args = {username, password};
return jdbcTemplate.queryForObject("select * from student where username=? and password=?", (rs, i) -> {
Student student = new Student();
student.setId(rs.getInt("id"));
student.setName(rs.getString("name"));
student.setUsername(rs.getString("username"));
student.setPassword(rs.getString("password"));
student.setSex(rs.getInt("sex"));
student.setAge(rs.getInt("age"));
student.setBirthday(rs.getDate("birthday"));
student.setCreateTime(rs.getTimestamp("create_Time"));
return student;
}, args);
}
@Override
public StudentClass getStudentClass(Integer id) {
Object[] args = {id};
return jdbcTemplate.queryForObject("select * from student_class sc where sc.class_no = (select class_no from student where id = ?)", (rs, i) -> {
StudentClass studentClass = new StudentClass();
studentClass.setClass_no(rs.getInt("class_no"));
studentClass.setClass_name(rs.getString("class_name"));
return studentClass;
}, args);
}
@Override
public List<Student> queryByClassNo(String name) {
Object[]args={name};
return jdbcTemplate.query("select * from student where class_no = (select class_no from Student_Class where class_name=?)",(rs,i)->{
Student student = new Student();
student.setId(rs.getInt("id"));
student.setName(rs.getString("name"));
student.setUsername(rs.getString("username"));
student.setPassword(rs.getString("password"));
student.setSex(rs.getInt("sex"));
student.setAge(rs.getInt("age"));
student.setBirthday(rs.getDate("birthday"));
student.setCreateTime(rs.getTimestamp("create_Time"));
return student;
},args);
}
}
Test
public static void main(String[] args) {
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("applicationContext-jdbc.xml");
StudentService service = context.getBean(StudentService.class);
//新增
/* Student student = new Student();
student.setName("小黑");
student.setUsername("1008666");
student.setPassword("9998888");
student.setSex(0);
student.setAge(26);
student.setBirthday(new Date());
service.add(student);*/
}
解析:把方法变为一个事务,方法出现异常时事务回滚。
@Transactional注解与配置文件的使用
xml文件,去掉了通知和切入点的声明和关联。
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:tx="http://www.springframework.org/schema/tx"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx.xsd">
<!--包扫描-->
<context:component-scan base-package="com.entor.jdbc"/>
<!--
配置数据源
负责初始化数据库连接池
-->
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
<property name="driverClassName" value="oracle.jdbc.driver.OracleDriver"/>
<property name="url" value="jdbc:oracle:thin:@localhost:1521:XE"/>
<property name="username" value="jsd2101"/>
<property name="password" value="jsd2101"/>
</bean>
<!--
配置Spring数据源事务管理器
用来管理数据库连接池内的事务的提交和回滚
-->
<bean id="dataSourceTransactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"/>
</bean>
<!--
配置事务传播特性,设置目标对象的那些方法中使用事务管理
设置事务管理者
方法名以add,save,del,update,modify,remove开头的需要事务管理。
除此以外的不需要事务管理,只读操作。
-->
<!--使用注解驱动把事务添加在目标对象的方法中,目标对象方法需要使用事务则在目标对象上添加注解@Transactional-->
<tx:annotation-driven transaction-manager="dataSourceTransactionManager"/>
<!--设置数据库操作对象-->
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<property name="dataSource" ref="dataSource"/>
</bean>
</beans>
Dao层
//删除,该方法开启事务
@Transactional
public void deleteById(Integer id) {
int i = jdbcTemplate.update("delete student where id = " + id);
System.out.println("删除了" + i + "条数据");
}
放在类上则默认所有方法开启事务。
@EnableTransactionManagement的使用(去掉配置配置文件)
properties文件
jdbc.driver=oracle.jdbc.driver.OracleDriver
jdbc.url=jdbc:oracle:thin:@localhost:1521:XE
jdbc.username=jsd2101
jdbc.password=jsd2101
配置类
@Configuration
@PropertySource(value = "db.properties")//加载类路径下的资源配置文件文件,结合@Value注解使用
@ComponentScan(value = "com.entor.jdbc")//组件包扫描,扫描指定包以及子包,加载该包下的所有注解。
@EnableTransactionManagement//开启事务管理,等价于配置文件的<tx:annotation-driven transaction-manager="dataSourceTransactionManager"/>
public class SpringConfig {
@Value("${jdbc.driver}")//从配置文件中根据键的值读取对应的值赋给属性
private String driver;
@Value("${jdbc.url}")
private String url;
@Value("${jdbc.username}")
private String username;
@Value("${jdbc.password}")
private String password;
@Bean
public DataSource dataSource(){
DruidDataSource dataSource = new DruidDataSource();
dataSource.setDriverClassName(driver);
dataSource.setUrl(url);
dataSource.setUsername(username);
dataSource.setPassword(password);
return dataSource;
}
@Bean
public TransactionManager transactionManager(){
return new DataSourceTransactionManager(dataSource());
}
@Bean
public JdbcTemplate jdbcTemplate(){
return new JdbcTemplate(dataSource());
}
}
Dao层
//删除,该方法开启事务
@Transactional
public void deleteById(Integer id) {
int i = jdbcTemplate.update("delete student where id = " + id);
System.out.println("删除了" + i + "条数据");
}
Test
//开启组件包扫描后,直接访问该类即可,其他注解的功能交给组件包扫描。
public class Test2 {
public static void main(String[] args) {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(StudentServiceImpl.class);
StudentServiceImpl studentService = context.getBean(StudentServiceImpl.class);
studentService.queryById(10);
}
}
补充@ImportResource(location=“classpath:”***.xml")
导入配置文件,相当于在一个配置文件中导入另一个配置文件
Spring Bean的生命周期注入
单个实体类对象注入
实体类中实现接口:InitializingBean,DisposableBean
方法:
@Override
public void afterPropertiesSet() throws Exception {
System.out.println("User对象初始化之前");
}
@Override
public void destroy(){
System.out.println("User对象初始化之后");
}
所有实体类对象注入:
在配置类中实现接口:InstantiationAwareBeanPostProcessor
实例:
@Configuration
public class Confi implements InstantiationAwareBeanPostProcessor {
@Bean(initMethod = "init",destroyMethod = "destroy")
public User user(){
return new User(1,"张三",20);
}
@Override
public Object postProcessBeforeInstantiation(Class<?> beanClass, String beanName) throws BeansException {
System.out.println("所有对象实例化之前");
return null;
}
@Override
public boolean postProcessAfterInstantiation(Object bean, String beanName) throws BeansException {
System.out.println("所有对象实例化之后");
return true;
}
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
System.out.println("所有对象初始化之前");
return null;
}
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
System.out.println("所有对象初始化之后");
return null;
}
}