分层开发
- 表现层:
- 直接和用户打交道,大部分跟界面有关;
- 服务层:
- 业务逻辑,由一个到多个基本的增删改查组成;
- 持久层(数据访问层):
- 将数据永久的保存,jdbc、mybatis;
Spring框架
特点
- 将其他的框架进行整合,便于开发,提高程序的扩展性;
- 声明式的事务管理:不需要编码进行事务控制,可以用xml的配置文件、用注解的方式;
spring 框架的核心思想
IOC(inversion of controll 控制反转)
public class MyServlet extends HttpServlet{
service
doGet
doPost
}
//对于servlet类,并不需要程序员自己来创建实例对象,而是由tomcat来创建MySevlet
//的实例对象,由tomcat来调用servlet中的方法;就是把servlet控制权交给tomcat容器;
概念
所谓的控制反转,就是把对象的一些控制权(对象的创建,一些方法的调用)都交给容器来完成;
以后可以把很多对象的控制权交给spring容器来管理,对象的创建、对象的生命周期、对象的个数、对象的依赖关系;
spring中的IOC
- 添加spring依赖
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>4.3.22.RELEASE</version>
</dependency>
- 编写配置文件
提供一个xml的配置文件,resources=>spring.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"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd">
</beans>
- 编写一个java类交给spring
public class UserService {
public void insert(User user){
System.out.println("添加用户");
}
}
使用一个bean标签,把某一个类交给spring容器管理;
<!-- spring 的配置
id="唯一标识"
class="包名.类名"
-->
<bean id="userService" class="service.UserService"></bean>
- 根据配置文件创建spring容器
在spring初始化时根据bean标签创建实例;
public class TestSpring {
public static void main(String[] args) {
//1.创建spring容器
//classpath类路径、application 应用程序、context容器 根据配置文件创建的应用程序spring容器
ClassPathXmlApplicationContext context =
new ClassPathXmlApplicationContext("spring.xml");
//2.1 根据id获取容器中的对象
UserService userService=(UserService) context.getBean("userService");
//2.2 根据class类型获取容器中的对象
UserService service1 = context.getBean(UserService.class);
//3.使用对象
service1.insert(new User());
}
}
spring容器控制反转都能控制那些方面
- 对象的个数
默认情况下一个bean标签,只会创建一个对象(单例);
如果想使用一次就创建一个对象,在bean标签中加入:
<!-- prototype表示多例 -->
<bean id="userService" class="service.UserService" scope="prototype"></bean>
- 控制对象的生命周期方法
对于单例对象来讲,容器一创建就会创建这些单例对象,并且随后调用他们的初始化方法;
对于多例,每次使用多例时就会创建一个新的对象,并调用他们的初始化方法;对于多例对象,销毁方法无效;
初始化方法:实例创建时调用
<bean init-method="初始化方法名">
销毁方法:容器关闭时调用;
<bean destroy-method="销毁方法名">
- 控制是否懒惰初始化
用到对象时才创建其实例,默认情况下是饿汉;
<bean lazy-init="true">
还可以配置全局bean,在beans标签中配置
default-lazy-init="true"
- 控制对象之间的依赖关系
案例:控制UserDao对象和UserService对象
public class UserDao {
public void insertDao(){
System.out.println("访问数据库 insert");
}
}
因为UserDao是私有属性,所以用set方法赋值;
public class UserService {
private UserDao userDao;
public void setUserDao(UserDao userDao) {
this.userDao = userDao;
}
public void insert(User user){
System.out.println("添加用户");
userDao.insertDao();
}
public void init(){
System.out.println("初始化方法");
}
public void destroy(){
System.out.println("销毁方法");
}
}
在配置文件中,创建UserDao对象的bean标签,并且配置userService的userDao属性;
<bean id="userService"
class="service.UserService"
scope="singleton"
init-method="init"
destroy-method="destroy">
<!--property属性名
ref 属性对象bean标签id-->
<property name="userDao" ref="userDao"></property>
</bean>
<bean id="userDao"
class="dao.UserDao"></bean>
降低层与层之间的耦合度,层与层之间要依赖接口,而不要依赖具体实现;
依赖注入
DI (dependency inject):建立对象之间依赖关系的过程和方式;
上面的案例把userService 需要的userDao注入给userService属性,使用set方法赋值称为set方法注入
常见方式:
- set方法注入,spring调用set方法来完成对属性的赋值;
- 利用构造方法进行注入,spring底层调用构造方法完成对属性的赋值;
<!--index:构造方法的参数下标,从0开始,ref 属性对象对应的bean标签id
<constructor-arg index="0" ref="userDao"></constructor-arg>
<bean id="userDao"
class="dao.impl.UserDaoFile"></bean>
简化依赖注入的办法
autowired 自动织入:要么根据名字匹配,要么根据类型匹配;
- byName:可以唯一确定容器中的bean
autowire="byName"
- byType:当容器中有多个类型相同的bean,就不适用了;
autowire="byType"
- constuctor:根据构造方法去匹配;
注解方式的注入
@AutoWired 可以加在要注入的属性上,也可以加在对应的set方法或构造方法上;
修改配置文件,添加context
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
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">
</beans>
<!--启动@AutoWired等注解-->
<context:annotation-config></context:annotation-config>
在构造器或者set方法添加注解
// @Autowired
// public UserService(UserDao userDao) {
// this.userDao = userDao;
// }
//
@Autowired
public void setUserDao(UserDao userDao) {
this.userDao = userDao;
}
值注入
set方法注入
package service;
public class OrderService {
private String name;
private int price;
public OrderService() {
}
public OrderService(String name, int price) {
this.name = name;
this.price = price;
}
public void setName(String name) {
this.name = name;
}
public void setPrice(int price) {
this.price = price;
}
public void print(){
System.out.println("name:"+name+"price"+price);
}
}
set方法注入
<bean id="orderService"
class="service.OrderService">
<property name="name" value="订单1"></property>
<property name="price" value="3000"></property>
</bean>
构造方法注入
<bean id="orderService"
class="service.OrderService">
<constructor-arg index="0" value="订单1"></constructor-arg>
<constructor-arg index="1" value="2000"></constructor-arg>
</bean>
注解注入
使用外部以.properties结尾的文件
文件内容
order.name=订单
order.price=5000
<!--读取配置文件 placeholder表示占位符-->
<context:property-placeholder location="classpath:a.properties" file-encoding="utf-8"></context:property-placeholder>
在spring之前,可以使用工厂方法+接口+配置文件的方式实现解耦合
- spring中的做法
@Autowired
private UserDao userDao; //被动获取,依赖注入
- 工厂方法的做法
private UserDao userDao=UserDaoFactory.getUserDao();
简化控制反转(spring2.5开始)
提供了注解方式的控制反转
@Component 加在类,spring扫描它之后,就把它所注解的类交给spring 容器管理,没有加注解的类则跳过;
@Component("userDao")
public class UserDaoImpl implements UserDao {
@Override
public void insert() {
System.out.println("访问数据 insert");
}
@Override
public void update() {
System.out.println("访问数据 update");
}
}
@Component("userDaoService") //相当于<bean id="userDaoService" class="UserDaoService" />
public class UserDaoService {
@Autowired
private UserDao userDao;
public void insert(){
System.out.println("UserService insert");
userDao.insert();
}
public void update(){
}
}
spring.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
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"
>
<!--启动注解@Autowired @Component...-->
<context:annotation-config></context:annotation-config>
<!--缩小搜索范围
component-scan里面涵盖了annotation-config的功能,所以annotation-config可以省略
-->
<context:component-scan base-package="service"></context:component-scan>
<context:component-scan base-package="dao"/>
</beans>
- @Controller(表现层或叫控制层)
- @Service(业务逻辑层)
- @Repository(对应数据访问层)
- @Component(不属于前三层,不好分类时使用)
加在类上
单例多例注解:@Scope("singleton|prototype")
控制懒惰初始化:@Lazy
加在类上 表示这个类在容器里时懒惰初始化的
加在方法上
初始化方法注解:@PostConstruct
销毁方法注解:@PreDetroy
缺点
注解方法具有一定局限性:比如用spring管理数据库连接池则不能使用注解,所以对于这种第三方的类使用xml比较好;
实际例子
spring管理连接池,结合Mybatis
添加依赖
</dependency>
<!--数据库依赖-->
<dependency>
<groupId>com.zaxxer</groupId>
<artifactId>HikariCp</artifactId>
<version>3.3.1</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.1.13</version>
</dependency>
<!--数据库驱动-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.47</version>
</dependency>
<!--Mybatis-->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.4.6</version>
</dependency>
<!--mybatis和spring结合-->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis-spring</artifactId>
<version>1.3.2</version>
</dependency>
<!--添加spring对jdbc以及transaction-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>4.3.22.RELEASE</version>
</dependency>
</dependencies>
接口文件HeroDao.class
public interface HeroDao {
@Insert("insert into hero (id,name,city,sex,birth,death,power)"+
"values (#{id},#{name},#{city},#{sex},#{birth},#{death},#{power})")
public void insert(Hero hero);
@Update("update hero set name=#{name}, city=#{city}, sex=#{sex}, birth=#{birth}, death=#{death}, power=#{power} where id=#{id}")
public void update(Hero hero);
@Delete("delete from hero where id=#{id}")
public void delete(int id);
@Select("select * from hero limit #{m}, #{n}")
public List<Hero> findAll(@Param("m") int a, @Param("n") int b);
@Select("select * from hero where id = #{id}")
public Hero findById(int id);
}
配置文件
- 配置连接池
配置连接池
```xml
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource" destroy-method="close">
<property name="url" value="jdbc:mysql://localhost:3306/test"/>
<property name="driverClassName" value="com.mysql.jdbc.Driver"/>
<property name="username" value="root"/>
<property name="password" value="westos"/>
<property name="maxActive" value="10"/>
<property name="minIdle" value="2"/>
</bean>
- 把mybatis中的SqlSessionFactory交给spring管理
<!--2.配置sqlSessionFactory-->
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<!--把连接池对象,依赖注入给SqlSessionfactoryBean的dataSource属性-->
<property name="dataSource" ref="dataSource"></property>
</bean>
- 把mybatis中的接口mapper交给spring容器管理
<!--3.配置有哪些mapper接口-->
<!--方法一:每个接口都要配置,比较麻烦-->
<!--<bean id="heroDao" class="org.mybatis.spring.mapper.MapperFactoryBean">-->
<!--<property name="mapperInterface" value="dao.HeroDao"/>-->
<!--<property name="sqlSessionFactory" ref="sqlSessionFactory"/>-->
<!--</bean>-->
<!--方法二:扫描dao包下有哪些接口需要给spring管理-->
<bean id="mapperScanner" class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<property name="basePackage" value="dao"></property>
</bean>
测试
public class TestMybatis {
public static void main(String[] args) {
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("spring.xml");
HeroDao service = context.getBean(HeroDao.class);
Hero hero = service.findById(1);
System.out.println(hero);
}
}
HikariCP 国外的 速度
Druid 阿里的 为监控
日志配置
- 添加日至相关的依赖
<!--添加日志依赖-->
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>1.2.3</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>jcl-over-slf4j</artifactId>
<version>1.7.2</version>
</dependency>
<dependency>
<groupId>commons-logging</groupId>
<artifactId>commons-logging</artifactId>
<version>1.2</version>
<scope>provided</scope>
- 提供 main/resources/logback.xml配置文件
<?xml version="1.0" encoding="UTF-8"?>
<configuration
xmlns="http://ch.qos.logback/xml/ns/logback"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://ch.qos.logback/xml/ns/logback logback.xsd">
<!-- 输出控制,格式控制-->
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>%date{yyyy-MM-dd HH:mm:ss} [%-5level] %logger{32} - %m%n </pattern>
</encoder>
</appender>
<appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
<!-- 日志文件名称 -->
<file>logFile.log</file>
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<!-- 每天产生一个新的日志文件 -->
<fileNamePattern>logFile.%d{yyyy-MM-dd}.log</fileNamePattern>
<!-- 保留 15 天的日志 -->
<maxHistory>15</maxHistory>
</rollingPolicy>
<encoder>
<pattern>%date{yyyy-MM-dd HH:mm:ss} [%-5level] %logger{17} - %m%n </pattern>
</encoder>
</appender>
<!-- 用来控制查看那个类的日志内容(对mybatis name 代表命名空间) -->
<logger name="dao.HeroDao" level="DEBUG" additivity="false">
<!--输出到控制台-->
<appender-ref ref="STDOUT"/>
<!--输入到文件-->
<appender-ref ref="FILE"/>
</logger>
<root level="ERROR">
<appender-ref ref="STDOUT"/>
</root>
</configuration>
声明式事务
- 配置文件添加tx标签
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:tx="http://www.springframework.org/schema/tx"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
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/tx http://www.springframework.org/schema/tx/spring-tx.xsd"
>
- 启用事务注解让
@Transactional
注解生效
<!--4. 启用事务注解-->
<tx:annotation-driven/>
- 启用事务管理器类
用来负责具体的事务管理操作(commit 、rollback)- DataSourceTransactionManager(基于数据源的事务管理器 对于mybatis来讲,选这个)
- HibernateTransactionManager(对于hibernate框架来讲)
<!--5. 事务管理器 id="transactionManager为默认名称-->
<bean id="transactionManager"
class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<!--指定连接池-->
<property name="dataSource" ref="dataSource"/>
</bean>
- 启动扫描
<!--6. 扫描service-->
<context:component-scan base-package="service"/>
注意:
- 事务控制代码不建议放到dao包,一般放在service,一般事务控制一般调用多条sql语句;
- spring中如果
@Transaction
的方法出现了RuntimeException或Error 事务回滚;@Transaction
的方法出现 Exception异常则事务提交; - 如果想让 Exception 事务回滚则添加
@Transaction(rollbackFor=Exception.class)
表示遇到什么样的异常则回滚;
@Service
public class HeroDaoService {
@Autowired
private HeroDao heroDao;
@Transactional(rollbackFor = Exception.class)
public void deleteByIds(List<Integer> list) throws Exception {
for (Integer id : list) {
if (id==8){
throw new Exception("模拟exception异常");
}
heroDao.delete(id);
}
}
}
测试
public class TestMybatis {
public static void main(String[] args) throws Exception {
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("spring.xml");
HeroDaoService heroDaoService = context.getBean(HeroDaoService.class);
//事务提交成功
heroDaoService.deleteByIds(Arrays.asList(1,2,3));
//出现RunTimeException异常 事务回滚
heroDaoService.deleteByIds(Arrays.asList(4,5,6));
//出现Exception异常 事务提交
heroDaoService.deleteByIds(Arrays.asList(4,5,6));
//添加配置@Transactional(rollbackFor = Exception.class)
// 出现Exception异常 事务回滚
heroDaoService.deleteByIds(Arrays.asList(6,7,8));
}
}
Transactional注解的参数配置
-
readOnly 只读事务:在事务的执行过程中只执行查询操作,不执行修改操作,可以提高性能;如果在只读事务内执行了增删改它会报异常;
-
timeout 事务的超时时间:默认情况下事务是无限制时间的执行,直到事务执行完成才结束;为了避免超长事务影响整个系统的性能要设置超时时间,事务运行的时间越短越好;
-
isolation 隔离级别
- 未提交读 会产生脏读、不可重复读、幻读
- 提交读 不会产生脏读
- 可重复读 不会产生不可重复读
- 序列化读 没有上面的问题了
-
propogation 传播行为:两个service类中的方法相互之间调用的时候的行为;
- Propogation.SUPPORTS:支持的,不会主动开始事务,但是它会加入已经存在的事务;
- Propogation.REQUIRED(默认的):必须的,如果没有事务,那么开始事务,如果有事务就加入事务;
- Propogation.REQUIRES_NEW:需要新的,每次都会开始一个新事务
自定义注解
@Target
表示注解添加的位置
- ElementType.TYPE 表示该注解能够加在类上
- ElementType.METHOD 表示该注解能够加在方法上
- ElementType.FIELD 表示该注解能够加在属性上
@Retention
表示注解的作用范围 - Source:只对源代码有效;
- Class:对*.java源码和*.class字节码都有效;
- Runtime:对*.java源码和*.class字节码、运行期都有效