常用注解
常见的注解解析方法有两种:
- 编译期直接扫描:编译器在编译 Java 代码的时候扫描对应的注解并处理,比如某个方法使用@Override 注解,编译器在编译的时候就会检测当前的方法是否重写了父类对应的方法。
- 运行期通过反射处理:像框架中自带的注解(比如 Spring 框架的 @Value、@Component)都是通过反射来进行处理的
@Resource和@Autowired
都是做bean的注入时使用,其实**@Resource并不是Spring的注解**,它的包是javax.annotation.Resource,需要导入,但是Spring支持该注解的注入。
这个博客对常用注解讲的很详细且有例子 SpringBoot常用的50个注解
以下是根据苍穹外卖来整理的注解和使用实例。
1.@ResponseBody
的作用其实是将java对象转为json格式的数据,然后直接写入HTTP response 的body中;
@RequestMapping后,返回值通常解析为跳转路径,但是加上 @ResponseBody 后返回结果不会被解析为跳转路径,而是直接写入 HTTP response body 中。
2.@RestController
=@ReponseBody
+@Controller
在类上用@RestController,其内的所有方法都会默认加上@ResponseBody,也就是默认返回JSON格式。将控制器方法的返回值转换为JSON格式,并以HTTP响应的方式返回给客户端。@RestController和@Controller
的共同点是都用来表示Spring某个类是否可以接收HTTP请求,二者区别: @RestController无法返回指定页面,而@Controller可以;前者可以直接返回数据,后者需要@ResponseBody辅助。
在@RestController
中指定Bean的名称。
@RestController("adminShopController")//为了避免两个Bean都叫shopController的冲突,所以用为了区分他俩 @RequestMapping("/admin/shop") @Slf4j @Api(tags = "店铺相关接口") public class ShopController { }
@RestController("UserShopController") @RequestMapping("/user/shop") @Api(tags = "店铺相关接口") @Slf4j public class ShopController { }
3.@RequestBody
是作用在形参列表上,用于将前台发送过来固定格式的数据【xml格式 或者 json等】封装为对应的 JavaBean 对象,封装时使用到的一个对象是系统默认配置的 HttpMessageConverter进行解析,然后封装到形参上。
就是将JSON格式的数据封装到实体类中
4.@Transactional
是Spring框架中用于声明式事务管理的关键注解。通过使用@Transactional注解,我们可以更加方便地管理事务,保障数据的一致性和可靠性。用于声明式事务管理**,保证方法或类中的操作在同一个事务中执行**。
@Transactional 的作用范围方法:推荐将注解使用于方法上,不过需要注意的是:该注解只能应用到 public 方法上,否则不生效。类:如果这个注解使用在类上的话,表明该注解对该类中所有的 public 方法都生效。接口:不推荐在接口上使用。
@Transactional 注解使用详解
(1)@Transactional
的作用范围:
方法:推荐将注解使用于方法上,不过需要注意的是:该注解只能应用到 public 方法上,否则不生效。
类:如果这个注解使用在类上的话,表明该注解对该类中所有的 public 方法都生效。
接口:不推荐在接口上使用。
(2)常用配置参数
propagation 代表事务的传播行为,默认值为 Propagation.REQUIRED,其他的属性信息如下:
Propagation.REQUIRED
:如果当前存在事务,则加入该事务,如果当前不存在事务,则创建一个新的事务。( 也就是说如果A方法和B方法都添加了注解,在默认传播模式下,A方法内部调用B方法,会把两个方法的事务合并为一个事务 )- Propagation.SUPPORTS:如果当前存在事务,则加入该事务;如果当前不存在事务,则以非事务的方式继续运行
- Propagation.MANDATORY:如果当前存在事务,则加入该事务;如果当前不存在事务,则抛出异常。
Propagation.REQUIRES_NEW
:重新创建一个新的事务,如果当前存在事务,暂停当前的事务。( 当类A中的 a 方法用默认
Propagation.REQUIRED模式,类B中的 b方法加上采用 Propagation.REQUIRES_NEW模式,然后在 a 方法中调用 b方法操作数据库, 然而 a方法抛出异常后,b方法并没有进行回滚,因为Propagation.REQUIRES_NEW会暂停 a方法的事务 )- Propagation.NOT_SUPPORTED:以非事务的方式运行,如果当前存在事务,暂停当前的事务。
- Propagation.NEVER:以非事务的方式运行,如果当前存在事务,则抛出异常。
Propagation.NESTED
:和 Propagation.REQUIRED 效果一样。
(3)同一个类中方法调用,导致@Transactional
失效
开发中避免不了会对同一个类里面的方法调用,比如有一个类Test,它的一个方法A,A再调用本类的方法B(不论方法B是用public还是private修饰),但方法A没有声明注解事务,而B方法有。则外部调用方法A之后,方法B的事务是不会起作用的。这也是经常犯错误的一个地方。
(4)@Transactional 注解属性 rollbackFor 设置错误
rollbackFor 可以指定能够触发事务回滚的异常类型。Spring默认抛出了未检查unchecked异常(继承自 RuntimeException 的异常)或者 Error才回滚事务;其他异常不会触发回滚事务。如果在事务中抛出其他类型的异常,但却期望 Spring 能够回滚事务,就需要指定 rollbackFor属性。
若在目标方法中抛出的异常是 rollbackFor 指定的异常的子类,事务同样会回滚。
所以一般使用 @Transactional(rollbackFor = Exception.class)
就可以了,但可以根据业务自定义异常类
事务
(1)事务是逻辑上的一组操作,要么都执行,要么都不执行。
(2)事务的特性(ACID):
原子性(Atomicity):事务是最小的执行单位,不允许分割。事务的原子性确保动作要么全部完成,要么完全不起作用;
一致性(Consistency):执行事务前后,数据保持一致,例如转账业务中,无论事务是否成功,转账者和收款人的总额应该是不变的;
隔离性(Isolation):并发访问数据库时,一个用户的事务不被其他事务所干扰,各并发事务之间数据库是独立的;
持久性(Durability):一个事务被提交之后。它对数据库中数据的改变是持久的,即使数据库发生故障也不应该对其有任何影响。
只有保证了事务的持久性、原子性、隔离性之后,一致性才能得到保障。也就是说 A、I、D 是手段,C 是目的!
(3)Spring 支持两种方式的事务管理
【1】编程式事务管理:通过 TransactionTemplate或者TransactionManager
手动管理事务,实际应用中很少使用
【2】声明式事务管理:实际是通过 AOP 实现(基于@Transactional 的全注解方式使用最多)
5.@RequestMapping
映射请求URL和处理方法。跳转路径
6.@GetMapping
用于映射HTTP GET请求。
7.@PostMapping
用于映射HTTP POST请求。
8. @RequestParam
用于获取请求参数的值。
@DeleteMapping @ApiOperation("菜品批量删除") public Result delete(@RequestParam List<Long> ids){//注解@RequestParam,可以将地址栏中多个数字参数提取出来然后变成List集合。 log.info("菜品批量删除:{}",ids); dishService.deleteBatch(ids); return Result.success(); }
9. @ConfigurationProperties
注解代表当前类是一个配置属性类,作用是:封装配置文件中的一些配置项。
原理就是:通过配置属性类,将配置文件中的配置项,封装成一个类,然后通过@Autowired注解注入到要使用的地方。
10.取的是路径参数,加注解@PathVariable
,如果和路径参数不同名,就要加括号双引号指明取的是哪个路径参数@PathVariable("status")
;如果同名,就不用加。
@PostMapping("/status/{status}") @ApiOperation("启用禁用员工账号") public Result startOrStop(@PathVariable Integer status,Long id){ log.info("启用禁用员工账号:{},{}",status,id); employeeService.startOrStop(status,id); return Result.success(); }
- AOP相关注解
切入点:@Pointcut
里面写的是对哪些方法进行拦截,要满足2点:
①必须是mapper下的所有类的方法,
②还要有AutoFill这个注解。
在对切面的定义中共使用@Pointcut("execution(* com.sky.mapper.*.*(..)) && @annotation(com.sky.annotation.AutoFill)")
这里指定使用的包范围和在有注解标注的方法上。
mapper中使用的示例:
@AutoFill(OperationType.UPDATE) void update(Employee employee);
(1)创建自定义注解
annotation.AutoFill.java 注解类@Target(ElementType.METHOD)/
/指定注解只能加载方法上,@Retention(RetentionPolicy.RUNTIME)
运行时
Target注解指定加上什么上面,Retention注解指定什么时候用,
@Target(ElementType.METHOD)//指定注解只能加载方法上 @Documented @Retention(RetentionPolicy.RUNTIME) public @interface AutoFill { //通过枚举-指定当前属性OperationType //数据库操作类型OperationType:就两种 Update 和 Insert OperationType value(); }
(2)切面类 aspect.AutoFillAspect.java 自动填充的切片类
@Aspect @Component @Slf4j public class AutoFillAspect { //这个包里的类和方法和加上@AutoFill注解的 @Pointcut("execution(* com.sky.mapper.*.*(..)) && @annotation(com.sky.annotation.AutoFill)") public void autoFillPointCut() { } //前置通知,在sql执行前加上即(公共字段赋值) @Before("autoFillPointCut()")//当匹配上切点表达式的执行这个 public void autoFill(JoinPoint joinPoint) {//插入链接点的参数值 //1.获取到当前被栏截的方法上的数据库操作类型 //2.获取到当前被拦截的方法的参数--实体对象 //3.准备赋值的数据 //4.根据当前不同的操作类型,为对应的属性通过反射来赋值 } }
12.Swagger使用注解
@Api(tags = “员工相关接口”)
@RestController @RequestMapping("/admin/employee") @Slf4j @Api(tags = "员工相关接口")//tags用来描述类的作用 public class EmployeeController {
@ApiOperation(“员工登录”)
@PostMapping("/login") @ApiOperation("员工登录") public Result<EmployeeLoginVO> login(@RequestBody EmployeeLoginDTO employeeLoginDTO) {
@ApiModel(description = “员工登录时传递的数据模型”)
@ApiModelProperty(“用户名”)
@Data @ApiModel(description = "员工登录时传递的数据模型") public class EmployeeLoginDTO implements Serializable { @ApiModelProperty("用户名") private String username; @ApiModelProperty("密码") private String password; }
13.@Bean
//项目启动时就会调用方法创建对象@ConditionalOnMissingBean/
/保证Spring容器里只有一个Util对象,条件对象当没Bean时再创建
注意要return的是这个新创建的对象,不然后面自动注入会失败,这里的主要目的就是创建Bean对象
@Bean //项目启动时就会调用方法创建对象 @ConditionalOnMissingBean//保证Spring容器里只有一个Util对象,条件对象当没Bean时再创建 public AliOssUtil aliOssUtil(AliOssProperties aliOssProperties) { return new AliOssUtil(aliOssProperties.getEndpoint(), aliOssProperties.getAccessKeyId(), aliOssProperties.getAccessKeySecret(), aliOssProperties.getBucketName()); }
因为涉及到多个表,所以添加@Transactional
的注解
(需要在启动类上添加@EnableTransactionManagement
注解):开启注解方式的事务管理
@Transactional//涉及几多个数据表,需要保证数据一致性,需要事务注解--保证原子性,全成功或全失败 @Override public void saveWithFlavor(DishDTO dishDTO) { //开始新增菜品 }
15.Spring Cache框架,实现了基于注解的缓存功能。
都是在Controller层:
(1)@CachePut
这个注释将方法的返回结果,user对象保存到Redis中,同时生成动态的key,userCache::user.id
@CachePut(cacheNames="userCache",key="abs")//Spring Cache缓存数据,key的生成:userCache:abc @CachePut(cacheNames="userCache",key="#user.id")//与形参保持一致,或者 @CachePut(cacheNames="userCache",key="#result.id")//返回值result,或者 @CachePut(cacheNames="userCache",key="#p0.id")//获得当前方法的第一个参数user,或者 @CachePut(cacheNames="userCache",key="#a0.id")//获得当前方法的第一个参数user,或者 @CachePut(cacheNames="userCache",key="#root.args[0].id")//获得当前方法的第一个参数user public User save(@RequestBody User user){ userMapper.insert(user); return result; }
插入完数据后,数据库生成的主键值会自动赋给user对象
Redis可以形成树形结构
(2)@Cacheable
注解
@Cacheable(cahceNames="userCache"),key="#id")//key的生成,userCache::10 public User getById(Long id){ User user = userMapper.getById(id); return user; }
(3)@CacheEvict
一次清理一条数据
@CacheEvict(cahceNames="userCache"),key="#id")//key的生成,userCache::10 public void deleteById(Long id){ userMapper.deleteById(id); }
清除所有数据
@CacheEvict(cahceNames="userCache"),allEntries=true)//userCache下的所有键值对 public void deleteAlld(){ userMapper.deleteAll(); }
动态sql
insert 返回生成的主键值
<insert id="insert" useGeneratedKeys="true" keyProperty="id"><!-- 产生的主键值会赋给id属性--> insert into dish (name, category_id, price, image, description, create_time, update_time, create_user, update_user, status) values (#{name}, #{categoryId}, #{price}, #{image}, #{description}, #{createTime}, #{updateTime}, #{createUser}, #{updateUser}, #{status}) </insert>
mapper:
不用注解,动态sql使用映射文件
在resource文件夹里都是xml文件
select
<select>
标签的id是mapper中的对应方法名,resultType是返回的参数类型。
<select id="selectByPage" resultType="com.sky.entity.Employee"> select * from employee <where> <if test="name!=null and name !='' "> and name like concat('%',#{name},'%') <!--模糊查询 like --> </if> </where> order by create_time desc <!--创建时间降序--> </select>
稍复杂一点的select
动态sql
sql语句:左外链接:将左边的表的所有项与右边的表作连接
将菜和种类两个表按照种类id链接起来,以此获得种类名称
select d.*,c.name as categoryName from dish d left outer join category c on d.category_id=c.id
由于种类名称查出来也叫name,所以给字段起别名
<select id="pageQuery" resultType="com.sky.vo.DishVO"> select d.*,c.name categoryName from dish d left join category c on d.category_id = c.id <where><!--毕竟动态sql。给它用where动态拼上DTO的3个属性 --> <if test="categoryId! =null">d.category_id=#{categoryId}</if> <if test="status!=null">d.status=#{status}</if> <if test="name!=null">d.name like concat('%',#{name},'%')</if> </where> order by d.create_time desc<!--根据创建时间降序 --> </select>
delete
(1)sql
按照菜id查全部信息,以此得到是否在售卖
@Select("select * from dish where id=#{id}") Dish getById(Long id);
(2)sql
是否有套餐关联
// select setmeal id from setmeal dish where dish_id in (1,2,3,4)
List queryUnsale(List ids);传入菜品List列表,用动态sql查每个菜品是否有套餐
SetMealDishMapper.xml
<select id="getSetmealIdsByDishIds" resultType="java.lang.Long"> select setmeal_id from setmeal_dish where dish_id in <foreach collection="dishIds" separator="," item="id" open="(" close=")"> #{id} </foreach> </select>
foreach循环,collection是集合,item是一个个项,separator是分割符号,open是开始符号,close是结束符号。每个元素用逗号分割,然后用大括号括起来。
(3)删除菜—传入为列表
<delete id="deleteBatch"> delete from dish where id in <foreach collection="ids" item="id" separator="," open="(" close=")"> #{id} </foreach> </delete>
(4)删除口味—传入为列表
<delete id="deleteBatchByDishIds"> delete from dish_flavor where dish_id in <foreach collection="ids" item="id" open="(" close=")" separator=","> #{id} </foreach> </delete>
update
<update id="update"> update dish <set> <if test="name!=null">name=#{name},</if> <if test="categoryId!=null">category_id=#{categoryId},</if> <if test="price!=null">price=#{price},</if> <if test="image!=null">image=#{image},</if> <if test="description!=null">description=#{description},</if> <if test="status!=null">status=#{status},</if> <if test="updateTime!=null">update_time=#{updateTime},</if> <if test="updateUser!=null">update_user=#{updateUser},</if> </set> where id=#{id} </update>