java-spring框架笔记
今天起开始java-spring框架的学习,在此以笔记的形式记录下学习过程,由于是从0开始的学习笔记,理解有错误的地方欢迎各位及时指正。
Spring框架简介
Spring是一个轻量级java开发框架,使用Spring框架的目的是为了为了减少开发过程中的重复工作,以及增强规范性,降低耦合性,其核心是控制反转(IOC)和面向切面(AOP)
Spring框架入门
1、部署使用框架
由于要使用maven来进行spring框架的开发,我们先配置好maven环境 在file-setting-Build…-Build Tools-Maven处配置maven环境路径,这里我使用了3.8.3版本的maven,这里需要注意的是java版本是否支持 待maven环境配置完成后,新建一个maven项目 创建完成后打开pom.xml,配置springframework,在这里添加几行代码更新maven即可 等待加载完成就配置好了
2、第一个spring程序
首先我们在com.itheima下创建一个HelloSpring类,包含一个set、方法和一个show方法 然后我们再applicationContext.xml里给username赋值 最后我们创建一个test类,来调用helloSpring的show方法,运行测试。
何为控制反转?何为依赖注入?
相信大家看完概念并做完第一个案例后依然无法准确的描述出到底什么是控制反转和依赖注入,那么我们可以先看一下上面的案例在没有使用框架技术的情况下该如何调用show方法: 很明显在这个test类中我们直接通过代码实例化出了HelloSpring类,然后调用set方法赋值,并调用show方法,这是最常见的写法。而在使用spring框架时我们是通过xml文件的标签给HelloSpring类的属性进行赋值的,通过xml文件进行赋值的过程就叫做依赖注入,依赖谁注入谁呢?某个类依赖IoC/DI容器对要实例化的外部资源类进行注入。这便是依赖注入的概念。 至于控制反转描述的同样是这个过程,只不过角度不同。在我们不用spring框架时,要调用某个外部资源类时都要自己去实例化并赋值,而控制反转描述的是在我们使用spring框架时,对外部资源的实例化和赋值都由IoC/DI容器进行控制,这些过程我们只需要对容器进行操作就可以实现,控制关系从程序控制转变为容器控制,这便是控制反转。
标签的作用及属性详解
1、常见标签
bean: 作用: 配置javaBean,让spring容器创建管理.默认调用类中无参数的构造方法创建对象 属性: id:唯一标识 class:类的全限定名称 scope:设置bean的作用范围,如sigleton单例或prototype多例 factory-method:指定实例工厂方法 factory-bean:指定实例工厂对象 constructor-arg: 作用:通过构造方法给成员变量赋值 属性: index:索引 name:指定成员变量在构造方法参数列表中的名称 type:指定类型 value:赋值 ref:给其他bean类型成员变量赋值 property: 作用:通过set方法给成员变量赋值 name:指定成员变量的名称 value:赋值 ref:给其他bean类型成员变量赋值 aop:config: 作用: 声明aop配置 aop:aspect: 作用: 配置切面
2、constructor-arg标签****的应用:
constructor-arg标签是基于构造方法实现的 首先创建一个User1类实现构造方法和toString方法 在applicationContext.xml里配置constructor-arg标签交给spring容器创建实例并赋值,这里的配置可以有三种方式,分别是使用name属性、type属性、index属性,但index属性要注意顺序,type属性需要留意多个参数相同的情况 创建test类进行测试
3、property标签的应用
property标签是基于set方法实现 同样创建一个user2类,实现set方法和toString方法 同样在xml里配置property标签,这里也有两种方式,第一种是在property配合name、value属性进行注入,第二种是使用ref,对另一个bean对象赋值,但这里会输出整的user1的对象而不会单独对每个属性赋值 创建test类 比较两次运行结果体会差异:
Bean的三种实例化方式
1、使用类构造器实例化(构造函数):直接通过Spring工厂返回类的实例对象。首先在类里写一个构造方法,在applicationContext.xml里进行赋值,通过测试类创建xml实例后getBean调用该类的方法。 2、使用静态工厂方法实例化(简单工厂模式):Spring工厂调用自定义工厂的静态方法返回类的实例对象。创建一个静态工厂方法,返回要调用的类,从xml里通过class和factory-method赋值,最后同样在test类里getbean调用该类的方法 3、使用实例工厂方法实例化(工厂方法模式):Spring工厂调用工厂的普通方法(非静态方法)返回类的实例对象。创建一个普通工厂方法,返回要调用的类,从xml里要写两个bean,其中一个
标签用来定义工厂类本身,利用容器去实例化方法,另一个 标签用来指定要调用的工厂方法和返回的bean类型,最后同样在test里getbean调用该类的方法
三种方式实例对比:
1、构造方法
创建一个简单的Bean1类 在xml里创建bean标签 test实现
2、静态工厂实例化
同样创建一个简单的Bean2类 创建静态工厂方法 在xml实现静态工厂 创建test类测试
3、实例化工厂:
创建Bean3类 在xml实现实例化工厂方法 写test类测试:
4、总结:
这三种方式的区别主要在于实例化Bean的方式不同。使用类构造器实例化是最简单的方式,只需要在配置文件中指定Bean的类名即可。使用静态工厂方法实例化和使用实例工厂方法实例化则需要在配置文件中指定工厂类和工厂方法。 这三种方式都可以用来实例化Bean,但是它们各自适用于不同的场景,下面两种开发的自由度更高。 为什么实例工厂方法实例化需要两个bean标签? 因为一个
基于注解的装配
注解是以‘@注解名’在代码中存在的,根据注解参数的个数,我们可以将注解分为:标记注解、单值注解、完整注解三类。和注释一样,注解不影响程序逻辑,但注解可以被编译或运行,相当于嵌入在代码中的补充信息
常用的注解功能介绍:
1、创建对象的注解 @Component:可以创建任意对象 @Controller:专门用来创建控制器的对象(Servlet),这种对象可以接收用户的请求,可以返回处理结果给客户端. @Service:专门用来创建业务逻辑层的对象,负责向下访问数据访问层,处理完毕后的结果返回给界面层. @Repository:专门用来创建数据访问层的对象,负责数据库中的增删改查所有操作. 2、依赖注入的注解 @Value:用来给简单类型注入值 @Autowired:使用类型注入值,从整个Bean工厂中搜索同源类型的对象进行注入. @Resource注解:使用name属性注入 3、Java自带的标准注解 @Override注解是用来指定方法重写的,只能修饰方法并且只能用于方法重写,不能修饰其它的元素。 @Deprecated可以用来注解类、接口、成员方法和成员变量等,用于表示某个元素(类、方法等)已过时。 @SuppressWarnings是用来抑制编译器警告 4、元注解 @Retention是用于说明注释的生命周期,即说明的注释在哪个范围内有效 @Target是用来表示注解作用范围,超过这个作用范围,编译的时候就会报错。 @Inherited是标记表明某个标记的类型被继承, @Documented是用来标注生成javadoc的时候是否会被记录 5、自定义注解 可以根据自己的需求定义注解 例如@interface 案例: 1、首先告诉spring使用注解开发 2、对Product属性进行赋值,使用content创建出三层对象,设置singleton单例 3、使用@Repository创建UserDao 4、使用@Service创建Productservice对UserDao(唯一标识)注入值 5、使用@Controller找到UserController,把刚刚创建的productService注入过来 6、运行测试类
Spring AOP
1、AOP中的一些基本概念:
通知/增强处理: 简单来说就是想要的功能,包含Aspect的一段代码处理 连接点: 任何允许通知的方法前后或者抛出异常时都可以是连接点,spring只支持方法连接点。 切入点: 切入点必是连接点,想要切入方法实现的连接点就是切入点 切面: 切入点和通知的结合 切入表达式:
首先要创建业务类,提前确认切入点,编写切入类,切入类需要在方法上方添加@before或@after来实现切面功能,切面表达式格式为@(切入位置) (value = “execution(权限 返回类型 切入位置)”),权限处可以留空,返回类型部分若无指定返回类型可以指定*,代表任意类型返回值。切入位置处通常是如下格式:com.xxxx.*.*(…)等格式,*在包路径后代表该包下的任意文件,在()前代表任意函数名,..在括号内代表任意返回值类型,了解切入点表达式是学习AOP最重要的一环。
2、AOP的两种实现方式:
XML方式
我们创建xml文件添加需要的bean标签,其包括实现类和唯一标识符。AOP的实现需要额外添加一条aop:aspectj-autoproxy用于绑定,作用是自动代理。若需要子类实现可以添加<aop:aspectj-autoproxy proxy-target-class=”true”>设置为子类代理,最后创建Test类测试即可
注解方式
在XML的实现基础上,注解方式并不需要改变太多。首先需要在业务类添加@Component注解用于实现该类,然后在切面类添加@Aspect注解,交给AspectJ的框架去识别切面类,且切面类本身也需要@Component去实现。到此,applicationContext的bean标签就可以删除了,取而代之的是注解实现必备的包扫描: <context:component-scan base-package=”com.bjpowernode.s01”>,其余的均不需要改动
实例: XML方式: 首先我们通过XML方式实现AOP,创建一个基本的计算器业务 写入我们的切入类,包括前置方法和后置方法: 在XML里实现业务方法和切面方法,并绑定aop自动代理,这里的aop标签是必备的: 创建test类进行测试: 注解方式: 这里基于XML方式修改 将业务类使用@component标签交给spring容器创建 切面类同样使用@component标签创建,同时还需要@Aspect 标签交给AspectJ的框架去识别切面类,添加before和after切入 XML里需要添加两条,首先注解需要包扫描,同时还需要aop的自动代理标签 到此就修改完成了 调用Test类测试:效果相同 这里涉及到一个proxy-target-class=”true”的作用,恰好老师上课问到了,这里就详细使用下面的案例来解释一下 这是一个基本的aop项目,项目存在impl接口的实现类,这里我们把本应该是someService的类型改成他的子类someServiceImpl类型: 此时运行会报错 这个时候把<aop:aspectj-autoproxy ></aop:aspectj-autoproxy>
改成<aop:aspectj-autoproxy proxy-target-class="true"></aop:aspectj-autoproxy>
,再重新运行一下,发现成功了 这个原理是什么呢? 在 Spring AOP 中,代理是用来在目标对象和调用者之间添加额外的行为(例如日志记录、安全检查等)。Spring AOP 可以使用两种不同的代理机制:JDK 动态代理和 CGLIB。 JDK 动态代理是通过实现目标对象的接口来创建代理。这意味着,如果您的目标对象实现了至少一个接口,那么 Spring AOP 将使用 JDK 动态代理来创建代理。但是,如果您的目标对象没有实现任何接口,那么 Spring AOP 将无法使用 JDK 动态代理。 在这种情况下,Spring AOP 将使用 CGLIB 来创建代理。CGLIB 是一个第三方库,它可以在运行时动态生成目标对象的子类,并将额外的行为添加到子类中。这样,即使您的目标对象没有实现任何接口,Spring AOP 也可以使用 CGLIB 来创建代理。 当在 <aop:aspectj-autoproxy>
中设置proxy-target-class="true"
时,强制 Spring AOP 使用 CGLIB 来创建代理,而不是 JDK 动态代理。这样即使目标对象实现了至少一个接口,Spring AOP 也将使用 CGLIB 来创建代理。
数据库开发
一般搭建网站的过程都会有与数据库的交互,这个章节我们学习数据库开发,采用mysql数据库和spring对接开发。
1、前期准备:
首先要配置好数据库环境,开启mysql数据库服务,创建相关数据库,这里我使用课上的案例直接导入数据库 在导入时发现了一个报错: 报错原因似乎是由于这个语句与我的版本不匹配,打开sql文件查看一下,报错位置在第一句: 这句话是用来检测如果存在这个数据库就将它删除掉的,所以既然报错我们删除这句也无伤大雅,删除后重新导入成功: 配置好数据库我们还要在pom中导入相关依赖: application.xml配置,重点在于账号密码要与数据库一致: 到此,前期准备结束
2、项目案例:
下面我们还是用一个学生信息的增删改查案例来了解spring如何进行数据库开发 首先创建一个student类,包含id、姓名、性别、生辰,并实现get、set方法 写一个dao方法类,定义对数据库进行增删改查的基础方法 在Impl里实现接口,我们先重点关注add方法 从这个示例中我们可以看出sql执行的基本流程:
1、定义一个JdbcTemplate对象,并提供getJdbcTemplate()和setJdbcTemplate()方法来设置和获取JdbcTemplate对象。 2、编写add方法,定义sql语句,获取传入的student类中各个参数传入到定义的Object类型数组中 3、使用JdbcTemplate的update方法将数组中的值填充到sql语句中执行
那接下来我们编写一个测试类来添加一条数据 运行后显示添加成功,可以在数据库中找到id=2的数据 其他的几个功能也是类似,只是sql语句不同 删除功能: 更改功能: 通过id查询功能:
3、其他问题:
编写的测试类也都大同小异,这里就不做具体演示了,下面说个容易出问题的点。 大多数我们使用数据库的时候都会出现要向数据库插入日期类型的情况,这里存在两个包 import java.util.Date;
import java.sql.Date;
导入的时候可能会混淆,但其实这两个都可以用,java.util.Date是java.sql.Date的父类,以2020-03-10为例,在使用java.util.Date的是时候我们需要使用 SimpleDateFormat 类的 parse 方法将字符串 “2020-03-10” 转换为日期类型,然后使用 student 对象的 setBorn 方法将转换后的日期设置为学生的出生日期 student.setBorn(new SimpleDateFormat("yyyy-MM-dd").parse("2020-03-10"));
而java.sql.Date只需直接输入日期即可 student.setBorn(Date.valueOf("2005-12-12"));
这两者效果是一样的,但不管使用哪种方法最终set的数据类型必须要和定义Student类的生日属性使用的包相同,个人建议如果没有特殊格式要求统一使用java.sql.Date会比较方便
4**、事务操作:**
很多情况下我们需要实现诸如转账之类的服务器与客户端或另一个客户端同步记录的功能,如果在一方操作完被报错中断没来得及对另一方进行操作就会出现数据操作,这个时候就需要用到事务操作,恰似”同进退,共生死”。 我们先通过XML方式实现一个删除与插入绑定的项目,体会事务的作用: 首先创建一个Student类实现getter、setter等方法 在接口类里定义一个事务方法 在接口实现类里重写出transfer方法,实现一个删除和插入方法 在xml里配置jdbc以及把studentDao交给spring容器创建 此时我们在数据表插入一条数据: 编写一个Test类,实现删除该条数据并插入一条新的数据 我们运行一下发现数据表中数据已经按照预想的结果更改了 这时我们重新插入name为zhangsan的数据,然后为了模拟一下在删除和重新插入之间出现报错的情况,在他们之间插入一条int i=1/0来模拟异常 此时我们再去运行test类,这里出现了报错 再看一下数据表的情况,可以看到删除操作执行了但插入操作并没有执行 那么如何避免这种情况呢?这里就用到了事务 我们在xml文件里配置事务 这里的配置不认识的参数很多,我们详细解释一下:
首先,定义了一个名为transactionManager的bean,它的类是org.springframework.jdbc.datasource.DataSourceTransactionManager。这个bean有一个名为dataSource的属性,它引用了另一个名为dataSource的bean,也就是我们定义的数据源。 然后,定义了一个名为txAdvice的事务建议,它使用了上面定义的transactionManager作为事务管理器。在这个建议中,定义了一个名为tx:attributes的元素,用于指定事务属性。在这个元素中,定义了一个名为tx:method的元素,用于指定方法级别的事务属性。这里指定了所有方法(name=“*”)都需要事务支持(propagation=“REQUIRED”),且不是只读事务(read-only=“false”),隔离级别为默认(isolation=“DEFAULT”),超时时间为-1(timeout=“-1”)。 最后,定义了一个名为aop:config的元素,用于配置AOP。在这个元素中,定义了一个名为aop:pointcut的元素,用于指定切入点表达式。这里指定了切入点表达式为execution(* cn.hdc.StudentDao.*(..)),表示匹配cn.hdc.StudentDao类中的所有方法。然后,定义了一个名为aop:advisor的元素,用于指定通知器。这个通知器引用了上面定义的txAdvice作为通知,并使用上面定义的切入点。
简而言之,这段代码配置了一个事务管理器,并使用AOP来将事务应用到cn.hdc.StudentDao类中的所有方法。 配置好事务我们重新插入名为ZhangSan的数据,再次运行test类,结果由于我们的模拟异常操作出现报错,这时我们再看数据库,发现数据没有被删除 通过这个案例我们了解了事务的用途,下面介绍使用注解的事务操作 再接口实现类中插入一行
propagation = Propagation.REQUIRED表示这个方法需要事务支持,如果当前没有事务,就新建一个事务;如果当前已经有事务,就加入到当前事务中。 isolation = Isolation.DEFAULT表示使用默认的隔离级别。隔离级别用于控制多个事务之间的可见性,以防止脏读、不可重复读和幻读等问题。 readOnly = false表示这个事务不是只读的。只读事务通常用于查询操作,可以提高性能。
这句话声明了一个方法需要事务支持,使用默认的隔离级别,并且不是只读的。 然后我们在xml配置文件里添加一行配置,用于开启注解式声明事务 这样实现事务的效果和xml方式是相同的,运行test后同样因为异常报错,但数据库中数据回滚不变。 实现过程中遇到的问题: 在实现项目时,多次检查代码配置无误后发现事务依然不生效,再寻求老师的帮助后我了解到问题出现在储存引擎 这里是我创建数据库的语句: CREATE TABLE stu (
sid INT PRIMARY KEY AUTO_INCREMENT,
sname VARCHAR(20),
age VARCHAR(20),
course VARCHAR(20)
);
这个是老师创建数据库使用的语句: CREATE TABLE `stu` (
`sid` int(5) NOT NULL AUTO_INCREMENT COMMENT '学生编号',
`sname` varchar(20) DEFAULT NULL,
`age` varchar(20) DEFAULT NULL,
`course` varchar(20) DEFAULT NULL,
PRIMARY KEY (`sid`)
) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8mb4;
主要区别在于老师给出的语句指定了储存引擎为InnoDB,而我使用的创建语句默认为MyISAM引擎,是不支持事务操作的,所以这里在使用事务时也要格外注意
Mybatis框架
MyBatis 是一款优秀的持久层框架,它支持自定义 SQL、存储过程以及高级映射。 MyBatis 免除了几乎所有的 JDBC 代码以及设置参数和获取结果集的工作。 它能把数据保存在数据库中,也能从数据库中读取数据,使用了Mybatis可以减少代码的重复,提高了开发效率。 下面我们通过一个Mybatis项目实例来了解该框架的配置使用以及优点 数据库中有一个学生数据表,具体结构如下: 要求使用Mybatis框架完成增删改查的操作 首先我们在pom.xml
里引入Mybatis需要的环境,这几项是必须的: 然后根据数据库配置jdbc 创建一个学生类,实现有参和无参构造方法,创建getter,setter方法: 我把重心放在mybatis配置文件的理解,首先看第一个配置文件:mybatis-config.xml
这里的
标签,类型别名是为 Java 类型设置一个短的名称。它只用于 XML 配置,可以简化我们的书写。在这个例子中, 指定了一个包名,MyBatis 会在包 cn.hdc.ch5.pojo 中搜索需要自动为其创建别名的 Java Bean,别名默认为类名,也就是说如果我们cn.hdc.ch5.pojo下存在一个student类,我们只需直接使用student即可,而不再需要cn.hdc.ch5.pojo.student 在这个标签中MyBatis 可以配置多种 环境,这样就可以在同一配置文件中切换不同的数据库。default 属性指定了默认环境。在这个例子中,默认使用为 development 的环境。 下面的transactionManager配置了事务管理器的类型为JDBC,类型为POOLED连接池,下面引入了我们数据库中的配置 最关键的是这里的mappers, 映射器是 Java 方法与 SQL 语句的桥梁。在这个例子中,配置了一个映射器文件 cn.hdc.ch5.mapper/StudentMapper.xml。
下一个要配置的文件是Mapper的xml文件:
开头
标签中的namespace定义了命名空间,下面通过在
主要的配置文件写完了,通常还需要编写个工具类
在这个工具类中,首先定义了一个salSessionFactory类型的静态变量sqlSessionFactory,SqlSessionFactory 是 MyBatis 的另一个核心接口,它用于创建 SqlSession 对象。 在下面的静态代码块中,使用 Resources.getResourceAsReader 方法读取 MyBatis 配置文件 mybatis-config.xml,然后使用 SqlSessionFactoryBuilder.build 方法根据配置文件创建 SqlSessionFactory 对象。最后在静态方法 getSqlsession 中,调用 sqlSessionFactory.openSession 方法来返回一个新的 SqlSession 对象。
到此所有关键部分都编写完成,创建test类进行测试。 test类代码千篇一律不做过多叙述: 增: 删: 改: 通过id查询: 查询表中全部内容:
动态SQL
之前学的mybatis框架相关内容都是实现简单的增删改查,但在实际的业务需求中并非只有增删改查那么简单,例如:获取业绩排行前十的人员姓名、展示年龄小于30岁的人员工号等等。 常用标签
if
if标签通常用于where语句后的判定,根据if语句的判定条件生成不同的sql语句,如下面这个案例中,如果username不为空,则拼接语句为select * from t_customer where 1=1 and username=(传入的参数) 若username为空,但job不为空则拼接后的语句为select * from t_customer where 1=1 and jobs=(传入的参数) https://lyblog-1309143339.cos.ap-beijing.myqcloud.com/java%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0/image94.png
choose、when、otherwise
观察下面这个案例,这三者配合起来和if其实差不多,但是逻辑变成了如果传入username就按username拼接,如果传入了jobs就用jobspinjie,如果二者都没有传入就按otherwise中的phone参数去拼接
where
在上面使用where语句时由于拼接语句时开头为and所以要拼接一个1=1恒真的条件,很容易忘掉导致错误,其实这里可以使用
trim、where、set
trim通常用来增删字符,下面这个例子里,使用prefix添加了一个where,用prefixOverrides删除语句中的and 而set标签就相当于sql语句中的set,通常与update联用,用于更新数据
foreach
很多时候我们查询的数据需要循环遍历,尤其是构建in语句时,在
遇到的相关问题:
关于xml配置文件位置的问题 在本次测试项目中Customer的xml配置文件没有放在resources目录下,这会导致有些时候出现无法找到xml文件的错误 解决办法: 1、在pom.xml添加一段扫描配置,让其自动检索 2、在资源目录下创建相同结构的xml文件
Mybatis关联映射
由于上节课缺席导致这一章节学的有些懵,抽出时间特地学习整理了下。
一对多
下面这个数据表中class和student是一对多的关系,一个班级可以有多个学生,但一个学生只对应一个班级 那么我们如何在java代码中体现出这种关系呢 基础配置到这个章节了就不详细说了,先看基础类 student我们使用StuIfo实体类表示 class使用StuClass实体类表示,由于是一对多,这里的学生使用list形式定义 重点在于下面的配置文件,先看student的配置文件
findclass测试
这里并没有映射StuInfo的stuClass属性,所以显示为空,如果映射的话就变成套娃了。可以看到一个班级中有多个学生的一对多关系成功表示了出来
findStu测试
这里一个学生对应一个班级的关系也成功表达出来。
多对多
这时我们再引入一个教师角色,一个教师可以教多个班级,而一个班级又有多个老师,这样老师和班级之间就是多对多的关系
教师类
班级信息类
在配置xml文件时,TeachInfo中的classes属性和ClassInfo中的teaches属性都要使用collection标签进行处理,其他配置基本相同 相对于一对多来说,多对多的映射关系原理大体上相同,但在实际编写过程中却出了很多问题,大多数问题是出在在数据表和sql语句上 实现这个多对多的过程中有三张表,一张教师信息表teach_info、一张班级信息表class_info、一张教师和班级对应关系表class_teach 最值得注意的是这张class_teach表 在编写sql语句时要依照class_id和teach_id的对应关系,语句编写如下:
SELECT c.*,t.* from teach_info t,class_info c,class_teach ct where c.cid = ct.class_id and ct.teach_id = t.tid and c.cid= #{cid}
同时select出信息也要包含所有resultmap映射的数据 例如我的sql语句如果这样写,故意缺省了t.tid:
SELECT c.*,t.tname,t.age,t.course from teach_info t,class_info c,class_teach ct where c.cid = ct.class_id and ct.teach_id = t.tid and c.cid= #{cid}
此时的运行结果tid就是null 修改成正确的语句,运行成功