欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

JavaWeb中点赞功能的实现及完整实例

程序员文章站 2022-04-15 15:35:25
实现原理1、功能描述:一个用户对同一文章只能点赞一次,第二次就是取消赞2、建立一个点赞表great,字段有文章ID(aid),点赞用户ID(uid)3、当有用户进行点赞行为时,使用aid和uid搜索点赞表。若有该记录,则表示用户已经点过赞,本次点击是取消点赞行为,故删除great表中的该条记录,同时 ......

实现原理
1、功能描述:一个用户对同一文章只能点赞一次,第二次就是取消赞
2、建立一个点赞表great,字段有文章id(aid),点赞用户id(uid)
3、当有用户进行点赞行为时,使用aid和uid搜索点赞表。

若有该记录,则表示用户已经点过赞,本次点击是取消点赞行为,故删除great表中的该条记录,同时将该文章的点赞数减1。
若无该记录,则表示用户是要点赞,故在great表中添加该记录,同时该文章的点赞数加1。
核心代码分析
核心控制器basecontroller:

@controller
public class basecontroller {
    private final greatrepository greatrepository;
    private final articlerepository articlerepository;
    //spring团队推荐的做法是用构造器来引入依赖,而不是直接使用@autowired引入
    @autowired
    public basecontroller(greatrepository greatrepository,
                          articlerepository articlerepository) {
        this.greatrepository = greatrepository;
        this.articlerepository=articlerepository;
    }

    @requestmapping({"/","/index"})
    public string index(){
        return "index";
    }

    //添加事务支持
    @transactional
    @requestmapping("/great")
    public string great(@param("aid") int aid, @param("uid") int uid, model model){
        //查询是否有该用户对该文章的点赞记录
        list<great> list=greatrepository.findbyaidanduid(aid,uid);
        if (list!=null && list.size()>0){
            //如果找到了这条记录,则删除该记录,同时文章的点赞数减1
            great great=list.get(0);
            //删除记录
            greatrepository.delete(great.getid());
            //文章点赞数减1,查询时使用mysql行级锁解决并发覆盖问题
            article article=articlerepository.findbyidforupdate(aid);
            article.setgreatnum(article.getgreatnum()-1);
            articlerepository.saveandflush(article);
        }else {
            //如果没有找到这条记录,则添加这条记录,同时文章点赞数加1
            great great=new great();
            great.setaid(aid);
            great.setuid(uid);
            //添加记录
            greatrepository.saveandflush(great);
            //文章点赞数加1,查询时使用mysql行级锁解决并发覆盖问题
            article article=articlerepository.findbyidforupdate(aid);
            article.setgreatnum(article.getgreatnum()+1);
            articlerepository.saveandflush(article);
        }
        model.addattribute("details",articlerepository.findall());
        return "detail";
    }

}


aritcle实体的持久化仓库articlerepository

@repository
public interface articlerepository extends jparepository<article,integer>{
    @lock(value = lockmodetype.pessimistic_write)
    article findbyidforupdate(integer id);
}

注意其中使用了@lock注解以及 lockmodetype.pessimistic_write来让jpa使用数据库层的行级锁,在事务中,该行级锁能解决对同一条记录的并发修改问题。

代码中已经附有详细注解

完整实例的相关信息
为了突出重点,项目前端较为简陋,功能已经通过测试。
项目采用的框架:
1、容器框架:springboot
2、持久层框架:spring data jpa
3、渲染框架:thymeleaf
4、版本控制:git
5、依赖:maven
6、数据库:mysql
数据库建表文件schema.sql:

drop table if exists `article`;
/*!40101 set @saved_cs_client     = @@character_set_client */;
/*!40101 set character_set_client = utf8 */;
create table `article` (
  `id` int(11) not null auto_increment,
  `num` int(11) default '0',
  primary key (`id`)
) engine=innodb auto_increment=3 default charset=utf8;
/*!40101 set character_set_client = @saved_cs_client */;

--
-- dumping data for table `article`
--

lock tables `article` write;
/*!40000 alter table `article` disable keys */;
insert into `article` values (1,1),(2,0);
/*!40000 alter table `article` enable keys */;
unlock tables;

--
-- table structure for table `great`
--

drop table if exists `great`;
/*!40101 set @saved_cs_client     = @@character_set_client */;
/*!40101 set character_set_client = utf8 */;
create table `great` (
  `id` int(11) not null auto_increment,
  `aid` int(11) not null,
  `uid` int(11) not null,
  primary key (`id`)
) engine=innodb auto_increment=6 default charset=utf8;
/*!40101 set character_set_client = @saved_cs_client */;

--
-- dumping data for table `great`
--

lock tables `great` write;
/*!40000 alter table `great` disable keys */;
insert into `great` values (5,1,1);
/*!40000 alter table `great` enable keys */;
unlock tables;
/*!40103 set time_zone=@old_time_zone */;


特别说明:本文章的目的只是单纯向大家说明点赞这个功能的实现思路。为了保证逻辑尽量清晰和简单,因而并没有涉及到性能优化。示例代码中的锁机制能保证并发访问下的安全性,但会对系统并发性能产生一定的影响,但在一般系统中,由于大量用户同时对同一文章集中点赞的情况并不常见,因此性能损耗扔在可以接受的范围内。

如果大家在使用过程中确实有高并发的需要,那么可以考虑使用redis这类缓存数据库来替代mysql。redis是高性能的内存kv数据库,且是单线程运行的,因此性能和安全性问题都能得到完美的解决。

关于jpa中如何使用行级锁,可以参考这篇文章:https://blog.csdn.net/fengyuxue11011/article/details/47039765