Day-two
1.1 数据源(连接池)的作用
1、数据源(连接池)是提高程序性能如出现的
2、事先实例化数据源,初始化部分连接资源
3、使用连接资源时从数据源中获取
4、使用完毕后将连接资源归还给数据源
5、常见的数据源(连接池):DBCP、C3P0、BoneCP、Druid等
开发步骤
①导入数据源的坐标和数据库驱动坐标
②创建数据源对象
③设置数据源的基本连接数据
④使用数据源获取连接资源和归还连接资源
1.2 数据源的手动创建【c3p0+druid】
①导入数据源的坐标和数据库驱动坐标
<!-- C3P0连接池 -->
<dependency>
<groupId>c3p0</groupId>
<artifactId>c3p0</artifactId>
<version>0.9.1.2</version>
</dependency>
<!-- Druid连接池 -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.1.10</version>
</dependency>
<!-- mysql驱动 -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.39</version>
</dependency>
②c3p0
@Test
public void testC3P0() throws Exception {
//创建数据源
ComboPooledDataSource dataSource = new ComboPooledDataSource();
//设置数据库连接参数
dataSource.setDriverClass("com.mysql.jdbc.Driver"); //注册驱动
dataSource.setJdbcUrl("jdbc:mysql://localhost:3306/test"); //连接数据库(ip+端口+数据库名)
dataSource.setUser("root");//数据库账户密码
dataSource.setPassword("root");
//获得连接对象
Connection connection = dataSource.getConnection();
System.out.println(connection);
}
③druid
@Test
public void testDruid() throws Exception {
//创建数据源
DruidDataSource dataSource = new DruidDataSource();
//设置数据库连接参数
dataSource.setDriverClassName("com.mysql.jdbc.Driver");
dataSource.setUrl("jdbc:mysql://localhost:3306/test");
dataSource.setUsername("root");
dataSource.setPassword("root");
//获得连接对象
Connection connection = dataSource.getConnection();
System.out.println(connection);
}
以上方式来使用数据库不方便解耦,怎么不方便法?你看setXXX设置都是固定的,写死的,这些是不是能够可以抽取?所以,我们要把数据库的一些信息放在配置文件中,到时候直接修改配置文件就好了。
那么,怎么操作?
①提取到jdbc.properties配置文件
jdbc.driver=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/test
jdbc.username=root
jdbc.password=root
②读取jdbc.properties配置文件创建连接池
@Test
public void testC3P0ByProperties() throws Exception {
//1、加载类路径下的jdbc.properties
ResourceBundle rb = ResourceBundle.getBundle("jdbc"); 【加载配置文件的名称】
//2、创建连接池对象
ComboPooledDataSource dataSource = new ComboPooledDataSource();
//3、设置数据库基本信息
dataSource.setDriverClass(rb.getString("jdbc.driver")); 【提取并设置数据库信息】
dataSource.setJdbcUrl(rb.getString("jdbc.url"));
dataSource.setUser(rb.getString("jdbc.username"));
dataSource.setPassword(rb.getString("jdbc.password"));
//4、创建数据库连接对象
Connection connection = dataSource.getConnection();
System.out.println(connection);
}
ResourceBundle
是Spring的工具类,用于加载运行时的类文件下(classes)的配置文件,这个运行时的classes文件,就相当于resourse下的配置文件。
1.3 Spring配置数据源
上边是使用配置文件来解决解耦问题,但是我们还可以脱离掉上边propocies配置文件,并且还可以继续解耦。
我们分析一下:
连接池对象获取数据库连接对象,是不是要依赖数据库的一些配置信息才能获取?
而且,你看下面的代码:
//3、设置数据库基本信息
dataSource.setDriverClass(rb.getString("jdbc.driver"));
dataSource.setJdbcUrl(rb.getString("jdbc.url"));
dataSource.setUser(rb.getString("jdbc.username"));
dataSource.setPassword(rb.getString("jdbc.password"));
是不是有很多setXXX的方法来设置数据库的配置信息呢?而这些方法正是连接池对象的,说明它的内部有setxxx方法和相关的成员变量。
所以,我们可以将创建连接池对象的控制权交给Spring,在配置bean的时候使用set方式来注入连接池对象依赖的信息。
具体怎么实现?
①pom中配置Spring基本包坐标(Spring)
②pom中配置相关的jar包坐标(数据库驱动包)
③配置applicationContext的Bean+注入依赖
<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
<property name="driverClass" value="com.mysql.jdbc.Driver"/>
<property name="jdbcUrl" value="jdbc:mysql://localhost:3306/test"/>
<property name="user" value="root"/>
<property name="password" value="root"/>
</bean>
解析:
- c3p0的连接池对象ComboPooledDataSource的全类名:
class="com.mchange.v2.c3p0.ComboPooledDataSource
- c3p0连接池对象的id唯一标识:
id="dataSource"
- ComboPooledDataSource中去掉setXXX的属性名:
property name="driverClass"
- ComboPooledDataSource中去掉setXXX的属性名的值:
value="com.mysql.jdbc.Driver"
对比一下:
dataSource.setDriverClass(rb.getString("jdbc.driver"));
dataSource.setJdbcUrl(rb.getString("jdbc.url"));
dataSource.setUser(rb.getString("jdbc.username"));
dataSource.setPassword(rb.getString("jdbc.password"));
④使用Spring容器获取连接池对象。
@Test
//测试:从Spring容器获取连接池对象
public void test02() throws Exception {
//1、加载配置文件,创建Spring容器
ApplicationContext app = new ClassPathXmlApplicationContext("applicationContext.xml");
//2、获取连接池对象
//DataSource dataSource = (DataSource) app.getBean("dataSource");
//ComboPooledDataSource dataSource = app.getBean(ComboPooledDataSource.class);
DataSource dataSource = app.getBean(DataSource.class);
//3、获取数据库连接对象
Connection connection = dataSource.getConnection();
System.out.println(connection);
}
我之前就很疑惑,为什么哪个连接池对象的类型不是ComboPooledDataSource而是DataSource ?
哦,原来前者是厂商提供实现标准接口的实现类,后者是标准的接口。(接口是sun公司的,实现类是不同数据库厂商来写)
这俩都可以,但是一般使用DataSource,因为接口嘛,比较通用,使用ComboPooledDataSource只代表c3p0的厂商,如果使用别的还得改代码。
1.4 抽取jdbc配置文件
<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
<property name="driverClass" value="com.mysql.jdbc.Driver"/>
<property name="jdbcUrl" value="jdbc:mysql://localhost:3306/test"/>
<property name="user" value="root"/>
<property name="password" value="root"/>
</bean>
从这里可以看出Spring配置Bean对象时,注入依赖的数据库信息都是写死的。因为JDBC的配置不是通过加载配置文件来进行动态获取的,如果我们使用其他非Mysql数据,我们还得修改Bean配置
最主要是,一般开发的习惯是:把Spring配置和一些配置文件分开操作。
因此,这里的解决方案是: 把JDBC的信息提取出来,写到配置文件中。
然后通过SPEL(类似JSP的EL表达式)使用键值对的KEY获取值。
代码演示:
①配置resource下的jdbc.properties
jdbc.driver=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/travel
jdbc.username=root
jdbc.password=root
②【applicationContext.xml引入jdbc配置文件】+【配置连接池对象】
③从Spring容器获取对象
@Test
//测试:从Spring容器获取连接池对象
public void test02() throws Exception {
//1、加载配置文件,创建Spring容器
ApplicationContext app = new ClassPathXmlApplicationContext("applicationContext.xml");
//2、获取连接池对象
//DataSource dataSource = (DataSource) app.getBean("dataSource");
ComboPooledDataSource dataSource = app.getBean(ComboPooledDataSource.class);
//3、获取数据库连接对象
Connection connection = dataSource.getConnection();
System.out.println(connection);
}
2.1 Spring原始注解
Spring是个轻代码、重配置的的框架,而我们都知道使用XML配置的过程是十分繁琐的,所以我们使用注解代替XML标签来提高开发效率。
什么是原始注解?
原始注解主要是代替XML的Bean配置。 替换就意味着,不需要写XML配置了。
原始注解图:
注解 | 说明 | 解释 | 参数 |
---|---|---|---|
@Component | 使用在类上用于实例化Bean | 代替XML的Bean的作用 | 该bean的ID标识(自定义) |
@Controller | 使用在web层类上用于实例化Bean | 语义化区分👆 | 该bean的ID标识 (自定义) |
@Service | 使用在service层类上用于实例化Bean | 语义化区分👆 | 该bean的ID标识(自定义) |
@Repository | 使用在dao层类上用于实例化Bean | 语义化区分👆 | 该bean的ID标识(自定义) |
@Autowired | 使用在字段上用于根据类型依赖注入 | 在成员变量位置上 | 无。表示自动注入 |
@Qualifier | 结合@Autowired一起使用用于根据名称进行依赖注入 | 在成员变量位置上 | 被注入的对象的ID标识 |
@Resource | 相当于@Autowired+@Qualifier,按照名称进行注入 | 在成员变量位置上 | 被注入的对象的ID标识 |
@Value | 注入普通属性 | 在成员变量上 | 需要注入的值,一般会跟SPEL使用 |
@Scope | 标注Bean的作用范围 | 在类上,表示Spring容器能够创建单个还是多个对象 | prototype:多例;singleton:单例 |
@PostConstruct | 使用在方法上标注该方法是Bean的初始化方法 | 在成员方法上 | 表示构造方法执行后执行某方法 |
@PreDestroy | 使用在方法上标注该方法是Bean的销毁方法 | 在成员方法上 | 表示在容器关闭时执行某方法 |
2.1.1 Spring原始注解:代码演示
XML方式:
此处省略了ApplicationContext.xml对于UserDaoIpml和UserServiceImpl的Bean配置,以及UserServiceImpl的依赖注入
UserDaoIpml
public class UserDaoImpl implements UserDao {
@Override
public void save() {
System.out.println("I'm dao。。。。。");
}
}
UserServiceIpml
public class UserServiceIpml implements UserService {
//成员变量
private UserDao userDao;
//成员变量的set方法
public void setUserDao(UserDao userDao) {
this.userDao = userDao;
}
//利用dao成员变量调用方法
@Override
public void save() {
userDao.save();
}
}
UserController(假设是web的控制层)
public class UserController {
//假设为web控制层
public static void main(String[] args) {
//1、加载配置文件,创建Spring容器
ApplicationContext app = new ClassPathXmlApplicationContext("applicationContext.xml");
//2、从容器内获取UserServiceImpl对象
UserService service = (UserService) app.getBean("userService");
//3、调用方法
service.save();
}
}
输出:
System.out.println("I'm dao。。。。。");
原始注解方式:
UserDaoImpl
使用@Compont或@Repository标识UserDaoImpl需要Spring进行实例化。
(告诉Spring,这个类你帮我创建)
/*
XML:
<!-- 配置UserDaoImpL-->
<bean id="userDao" class="cn.itcast.dao.Ipml.UserDaoImpl"></bean>*/
@Component("userDao") //自定义id标识
public class UserDaoImpl implements UserDao {
@Override
public void save() {
System.out.println("I'm dao。。。。。");
}
}
UserServiceIpml
/*
XML:
<!--配置UserService,并注入UserDao依赖-->
<bean id="userService" class="cn.itcast.service.Ipml.UserServiceIpml">
<property name="userDao" ref="userDao"></property>
</bean>
*/
@Component("userService")
public class UserServiceIpml implements UserService {
@Autowired
@Qualifier("userDao")
//成员变量
private UserDao userDao;
//成员变量的set方法
/* public void setUserDao(UserDao userDao) { 【XML使用set方法注入必须要有set方法,但是注解可以不用set方法来完成注入】
this.userDao = userDao;
}*/
//利用dao成员变量调用方法
@Override
public void save() {
userDao.save();
}
}
- 告诉Spring帮我进行实例化:
使用 【@Compont 】或 【@Service】标识UserServiceImpl
- userDao的注入:
使用【@Autowired】或者【@Autowired+@Qulifier】或者【@Resource】
那么问题来了,【@Autowired】、【@Autowired+@Qulifier】和【@Resource】
有什么区别:
- @Autowired:表示会按照数据类型从Spring容器进行匹配。
注意:它会先根据数据类型去容器里找对应的对象注入,如果该对象只有一个就直接注入成功;如果有多个同对象类型时,它会根据成员变量引用名称去容器里找,找到与之唯一匹配的对象id标识就注入成功。
- @Autowired+@Qulifier:对于前面的补充,即,能够分得清多个不同的同对象类型具体是什么。
- @Resource:相当于@Autowired+@Qulifier
使用方法是:Resource(name=”被注入的对象ID标识“)
有什么相同点?
被依赖注入的对象必须得在spring容器中存在:可以是注解配置,也可以是xml文件配置。
如果容器中没有被依赖注入的Bean就会报错
控制层
public class UserController {
//假设为web控制层
public static void main(String[] args) {
//1、加载配置文件,创建Spring容器
ApplicationContext app = new ClassPathXmlApplicationContext("applicationContext.xml");
//2、从容器内获取UserServiceImpl对象
UserService service = (UserService) app.getBean("userService");
//3、调用方法
service.save();
}
}
【记得】ApplicationContext.XML
一定要写注解扫描,不然Spring不知道解析哪里的注解。
<!--注解的组件扫描-->
<context:component-scan base-package="cn.itcast"/>
意思:
告诉Spring要去知道的目录去寻找注解去解析,不写组件扫描会报错。
输出:
System.out.println("I'm dao。。。。。");
你使用注解的方法跟XML配置的效果一样的,但是使用注解会比较更简单一点,我们加个注解就好了,不需要指定什么类的全类名。XML写对象的全类名是为了告诉Spring去创建指定路径的对象,而注解就不需要了,因为它在那个类上了,Spring能找到注解,自然也知道它的全类名。
2.1.2 Spring原始注解:代码演示
@Value:
前面对成员变量的注入都是对象引用类型的,Value注入的是普通属性的。
位置:放在成员变量上,
参数: 可以直接传具体的值,也可以是通过SPEL的KEY获得值
@Value("这是具体的值")
private String str1;
@Value("${jdbc.driver}") //这个是jdbc配置文件中的key
private String str2;
前提:
- 这个成员变量的类也被Spring容器创建
- 使用SPEL获取值,是从Spring容器中获取的,也就说,你得引入其他外部配置文件。(案列背景是:我们在此之前已经在applicationContext.xml引入jdbc的配置文件。)
@Scope:
位置:类上
参数:prototype:多例;singleton:单例
表示Spring容器是否可以创建单个或者多个Bean对象
//@Scope("prototype")
@Scope("singleton")
public class UserDaoImpl implements UserDao {
//此处省略代码
}
@PostConstruct与 @PreDestroy(了解)
位置:成员方法上。
作用: 指定某个方法在构造器执行之后,还是Spring容器结束之后执行
@PostConstruct
public void init(){
System.out.println("初始化方法....");
}
@PreDestroy
public void destroy(){
System.out.println("销毁方法.....");
}
2.2 Spring新注解
新注解:汇总:
注解 | 说明 | 解释 | 参数 |
---|---|---|---|
@Configuration | 用于指定当前类是一个 Spring 配置类,当创建容器时会从该类上加载注解 | 意思:如果注解在该类上,表示该类就变成配置类,这个类替代了XML配置文件 | 无 |
@ComponentScan | 用于指定 Spring 在初始化容器时要扫描的包。 作用和在 Spring 的 xml 配置文件中的 <context:component-scan base-package=“com.itheima”/>一样 | 代替XML中注解的组件扫描 | “指定要扫描注解的目录” |
@Bean | 使用在方法上,标注将该方法的返回值存储到 Spring 容器中 | 代替XML中Bean标签,用于创建对象 | “给对象起个名” |
@PropertySource | 用于加载.properties 文件中的配置 | 代替XML中的加载配置文件的标签 | “"classpath:配置文件名+类型” |
@Import | 用于导入其他配置类 | 向主配置类引入其他分配置类。 | “其他类.class” |
代码演示:
演示了配置类代替了XML的:
- 非自定义的Bean的配置:<bean>
- 加载properties文件的配置:<context:property-placeholder>
- 组件扫描的配置:<context:component-scan>
SpringCofiguration(代替了jdbc.properties)
//标志着该类为Spring的核心配置类
@Configuration
/*注解的组件扫描
<context:component-scan base-package="cn.itcast"/>*/
@ComponentScan("cn.itcast")
/*加载外部的properties配置文件
<context:property-placeholder location="classpath:jdbc.properties"/>*/
@PropertySource("classpath:jdbc.properties")
public class SpringCofiguration {
//定义成员变量并赋值
@Value("${jdbc.driver}")
private String driver;
@Value("${jdbc.url}")
private String url;
@Value("${jdbc.username}")
private String username;
@Value("${jdbc.password}")
private String password;
//定义方法,返回连接池对象。【相当于XML的bean,这个Bean不是我们自己写的是别人写的(数据库厂商)】
@Bean("dataSource")
public DataSource getDataSource() throws Exception {//返回值是你要创建的Bean
//1、创建连接池对象
ComboPooledDataSource dataSource = new ComboPooledDataSource();
//2、配置连接池信息
//dataSource.setDriverClass("com.mysql.jdbc.Driver");//注册mysql驱动包
dataSource.setDriverClass(driver);
//dataSource.setJdbcUrl("jdbc:mysql://localhost:3306/travel");//连接数据库
dataSource.setJdbcUrl(url);
//dataSource.setUser("root");//数据库登陆账户密码
dataSource.setUser(username);
// dataSource.setPassword("root");
dataSource.setPassword(password);
return dataSource;
}
测试类
@Test
//测试:从Spring容器获取连接池对象
public void test02() throws Exception {
//1、加载配置文件,创建Spring容器
//ApplicationContext app = new ClassPathXmlApplicationContext("applicationContext.xml");
ApplicationContext app = new AnnotationConfigApplicationContext(SpringCofiguration.class);
//2、获取连接池对象
//DataSource dataSource = (DataSource) app.getBean("dataSource");
ComboPooledDataSource dataSource = app.getBean(ComboPooledDataSource.class);
//3、获取数据库连接对象
Connection connection = dataSource.getConnection();
System.out.println(connection);
输出:connection地址
}
这里要说的是,既然配置类可以代替XML来配置Spring容器信息,那么也可以代替XML引入其他配置文件。同样都是为了解决这个问题:当所有乱七八糟的类别配置文件信息堆在一起会很乱,所以我们需要把1个配置类作为主配置文件,把分配置文件引入其中。
下面演示主配置类和分配置类的样子:
主配置类:SpringCofiguration
//标志着该类为Spring的核心配置类
@Configuration
//注解的组件扫描
@ComponentScan("cn.itcast")
//导入分配置类
@Import(DataSourceConfiguration.class) //参数:配置类.class
public class SpringCofiguration {
}
分配置类:DataSourceConfiguration
//加载外部的properties配置文件
@PropertySource("classpath:jdbc.properties")
public class DataSourceConfiguration {
@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("dataSource")
public DataSource getDataSource() throws Exception {
ComboPooledDataSource dataSource = new ComboPooledDataSource();
dataSource.setDriverClass(driver);
dataSource.setJdbcUrl(url);
dataSource.setUser(username);
dataSource.setPassword(password);
return dataSource;
}
测试类不变啊,跟上边一样
但是我这里有个疑问:
你这里是导入1个配置类啊,那我向导入多个配置类怎么办呢?
看这里:
参数变成数组:
@Import({DataSourceConfiguration.class,xxx.class.......})
3. Spring整合Junit
3.1 原始Junit测试Spring的问题
在测试类中,每个测试方法都有以下两行代码:
ApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml");
IAccountService as = ac.getBean("accountService",IAccountService.class);
这两行代码的作用是获取容器,如果不写的话,直接会提示空指针异常。所以又不能轻易删掉。
3.2 上述问题解决思路
让SpringJunit负责创建Spring容器,但是需要将配置文件的名称告诉它 【指:指定加载配置文件或者配置类的名称,不然它不知道你测试哪个配置文件的对象】
将需要进行测试Bean直接在测试类中进行注入【】
3.3 Spring集成Junit步骤
①导入spring集成Junit的坐标 【pom.xml导入Spring集成的Junit+基本包Junit】
②使用@Runwith注解替换原来的运行期 【runwith用谁来运行的意思。意思是:测试类的运行交给Spring的内核,Spring再交给Junit处理操作。以前我们测试时是直接找Junit的】
③使用@ContextConfiguration指定配置文件或配置类 【1、配置文件路径(classpath:xxx) 2、name:(classes:{配置类.class}):你得告诉我Spring容器在哪里】
④使用@Autowired注入需要测试的对象 【不需要创建对象了,直接@就可以注入对象】
⑤创建测试方法进行测试
代码实现:
①pom.xml导入Spring集成的Junit+基本包Junit
为什么导入Spring集成的Junit,还有导入基本包Junit?
因为前者依赖后者。
<!--此处需要注意的是,spring5 及以上版本要求 junit 的版本必须是 4.12 及以上-->
<!--Spring集成Junit-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>5.0.2.RELEASE</version>
</dependency>
<!-- junit基本包-->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>
②使用@Runwith注解替换原来的运行期
@RunWith(SpringJUnit4ClassRunner.class)
public class SpringJunitTest {
}
③使用@ContextConfiguration指定配置文件或配置类
@RunWith(SpringJUnit4ClassRunner.class)
//加载spring核心配置文件
//@ContextConfiguration(value = {"classpath:applicationContext.xml"})
//加载spring核心配置类
@ContextConfiguration(classes = {SpringConfiguration.class})
public class SpringJunitTest {
}
④使用@Autowired注入需要测试的对象
注入依赖对象就不需要New对象了,使用注解自动注入后直接可以使用(.)的方法是调用方法
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = {SpringConfiguration.class})
public class SpringJunitTest {
@Autowired
private UserService userService;
}
⑤创建测试方法进行测试
@RunWith(SpringJUnit4ClassRunner.class)
//加载spring核心配置类
@ContextConfiguration(classes = {SpringConfiguration.class})
public class SpringJunitTest {
@Autowired
private UserService userService;
@Test
public void testUserService(){
userService.save();
}
}