Spring Data JDBC、MySQL 和 Thymeleaf 实现分页、排序和多列LIKE过滤/搜索功能

  在本教程中,您将学习使用 Spring Data JDBC、MySQL 和 Thymeleaf 为现有 Spring Boot 应用程序实现分页、排序和过滤/搜索功能。

 

 

pom.xml

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.7.3</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>net.codejava</groupId>
    <artifactId>ProductManager</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>ProductManager</name>
    <description>Spring Boot Web App</description>

    <properties>
        <java.version>17</java.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-jdbc</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-thymeleaf</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>

        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <scope>runtime</scope>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-devtools</artifactId>
            <!-- <scope>true</scope>  -->
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
        </dependency>

    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

</project>

application.properties

spring.jpa.hibernate.ddl-auto=none
spring.datasource.url=jdbc:mysql://localhost:3306/sales?autoReconnect=true&useSSL=false&useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai
spring.datasource.username=root
spring.datasource.password=root
#logging.level.root=
#debug=true
logging.level.org.springframework.jdbc.core.JdbcTemplate=DEBUG



MySQL建表脚本

CREATE TABLE IF NOT EXISTS `product` (
  `id` int NOT NULL AUTO_INCREMENT,
  `name` varchar(45) COLLATE utf8mb4_unicode_ci NOT NULL,
  `brand` varchar(45) COLLATE utf8mb4_unicode_ci NOT NULL,
  `madein` varchar(45) COLLATE utf8mb4_unicode_ci NOT NULL,
  `price` float NOT NULL,
  PRIMARY KEY (`id`)
);

Application.java

package com.example;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class Application {

	public static void main(String[] args) {
		SpringApplication.run(Application.class, args);
	}

}

Product.java

package com.example.model;

import org.springframework.data.annotation.Id;
import org.springframework.data.relational.core.mapping.Table;

import lombok.Data;

@Table("product")
@Data // lomok
public class Product {

    @Id
    private Long id;
    private String name;
    private String brand;
    private String madein;
    private float price;

    public Product() {
    }

    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getBrand() {
        return brand;
    }

    public void setBrand(String brand) {
        this.brand = brand;
    }

    public String getMadein() {
        return madein;
    }

    public void setMadein(String madein) {
        this.madein = madein;
    }

    public float getPrice() {
        return price;
    }

    public void setPrice(float price) {
        this.price = price;
    }

}

ProductRepository.java

package com.example.repository;

import com.example.model.Product;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.repository.PagingAndSortingRepository;
import org.springframework.stereotype.Repository;

@Repository
public interface ProductRepository extends PagingAndSortingRepository<Product, Long> {

    Page<Product> findAllByNameContainingOrBrandContainingOrMadeinContaining(String name, String brand, String madein, Pageable pageable);

}

ProductController.java

package com.example.controller;

import com.example.model.Product;
import com.example.repository.ProductRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.servlet.ModelAndView;

import java.util.List;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;

@Controller
public class ProductController {

    private static final int[] PAGE_SIZES = {2, 5, 7, 10};

    @Autowired
    private ProductRepository repo;

    @RequestMapping("/")
    public String viewHomePage(Model model) {
        // String keyword = "reebok";
        String keyword = null;

        /*
		 * if (keyword != null) { return listByPage(model, 1, "name", "asc", keyword); }
         */
        return listByPage(model, 1, 2, "name", "asc", keyword);

    }

    @GetMapping("/page/{pageNumber}")
    public String listByPage(Model model, @PathVariable("pageNumber") int currentPage, @RequestParam("pageSize") int pageSize,
            @RequestParam("sortField") String sortField, @RequestParam("sortDir") String sortDir, @RequestParam(value = "keyword", required = false) String keyword) {

        Sort sort = Sort.by(sortField);
        sort = sortDir.equals("asc") ? sort.ascending() : sort.descending();

        Pageable pageable = PageRequest.of(currentPage - 1, pageSize, sort); // 7 rows per page
        Page<Product> page = null;
        if (keyword != null) {
            page = repo.findAllByNameContainingOrBrandContainingOrMadeinContaining(keyword, keyword, keyword, pageable);
        } else {
            page = repo.findAll(pageable);
        }

        long totalItems = page.getTotalElements();
        int totalPages = page.getTotalPages();
        int numberOfElements = page.getNumberOfElements();
        // int currentPage = page.previousPageable().getPageNumber();

        List<Product> listProducts = page.getContent();

        model.addAttribute("totalItems", totalItems);
        model.addAttribute("totalPages", totalPages);
        model.addAttribute("currentPage", currentPage);
        model.addAttribute("pageSize", pageSize);
        model.addAttribute("pageSizes", PAGE_SIZES);
        model.addAttribute("numberOfElements", numberOfElements);
        model.addAttribute("listProducts", listProducts); // next bc of thymeleaf we make the index.html

        model.addAttribute("sortField", sortField);
        model.addAttribute("sortDir", sortDir);
        model.addAttribute("keyword", keyword);

        String reverseSortDir = sortDir.equals("asc") ? "desc" : "asc";
        model.addAttribute("reverseSortDir", reverseSortDir);

        return "index";
    }

    @RequestMapping("/new")
    public String showNewProductForm(Model model) {
        Product product = new Product();
        model.addAttribute("product", product);

        return "new_product";
    }

    @RequestMapping(value = "/save", method = RequestMethod.POST)
    public String saveProduct(@ModelAttribute("product") Product product) {
        repo.save(product);

        return "redirect:/";
    }

    @RequestMapping("/edit/{id}")
    public ModelAndView showEditProductForm(@PathVariable(name = "id") Long id) {
        ModelAndView modelAndView = new ModelAndView("edit_product");
        Product product = repo.findById(id).get();
        modelAndView.addObject("product", product);

        return modelAndView;
    }

    @RequestMapping("/delete/{id}")
    public String deleteProduct(@PathVariable(name = "id") Long id) {
        repo.deleteById(id);

        return "redirect:/";
    }

}

index.html

<!-- http://localhost:8086/ -->

<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml"
      xmlns:th="http://www.thymeleaf.org">
    <head>
        <meta charset="utf-8" />
        <title>Product Manager</title>
    </head>

    <body>
        <div align="center">
            <div>
                <h1>Product Manager</h1>
                <a href="/new">Create New Product</a> <br />
                <br />
            </div>

            <div>
                <form th:action="@{/page/1}">
                    <input type="hidden" name="pageSize" th:value="${pageSize}" />
                    <input type="hidden" name="sortField" th:value="${sortField}" /> <input
                        type="hidden" name="sortDir" th:value="${sortDir}" /> Name Filter: <input
                        type="text" name="keyword" size="50" th:value="${keyword}" required />
                    &nbsp; <input type="submit" value="Search" /> &nbsp; <input
                        type="button" value="Clear" id="btnClear" onclick="clearSearch()" />
                </form>
            </div>

            <div>&nbsp;</div>

            <div>
                <table border="1" cellpadding="10">
                    <thead>
                        <tr>
                            <th><a
                                    th:href="@{'/page/' + ${currentPage} + '?sortField=id&sortDir=' + ${reverseSortDir} +'&pageSize=' + ${pageSize} + ${keyword != null ? '&keyword=' + keyword : ''}}">
                                    Product ID</a>
                            </th>
                            <th><a
                                    th:href="@{'/page/' + ${currentPage} + '?sortField=name&sortDir=' + ${reverseSortDir} +'&pageSize=' + ${pageSize}+ ${keyword != null ? '&keyword=' + keyword : ''}}">
                                    Name</a>
                            </th>
                            <th><a
                                    th:href="@{'/page/' + ${currentPage} + '?sortField=brand&sortDir=' + ${reverseSortDir}+'&pageSize=' + ${pageSize} + ${keyword != null ? '&keyword=' + keyword : ''}}">
                                    Brand</a>
                            </th>
                            <th><a
                                    th:href="@{'/page/' + ${currentPage} + '?sortField=madein&sortDir=' + ${reverseSortDir}+'&pageSize=' + ${pageSize} + ${keyword != null ? '&keyword=' + keyword : ''}}">
                                    Made In</a>
                            </th>
                            <th><a
                                    th:href="@{'/page/' + ${currentPage} + '?sortField=price&sortDir=' + ${reverseSortDir}+'&pageSize=' + ${pageSize} + ${keyword != null ? '&keyword=' + keyword : ''}}">
                                    Price</a>
                            </th>
                            <th><a>Actions</a></th>
                        </tr>
                    </thead>
                    <tbody>
                    <th:block th:each="product : ${listProducts}">
                        <tr >
                            <td th:text="${product.id}">Product ID</td>
                            <td th:text="${product.name}">Name</td>
                            <td th:text="${product.brand}">Brand</td>
                            <td th:text="${product.madein}">Made in</td>
                            <td th:text="${product.price}">Price</td>
                            <td><a th:href="@{'/edit/' + ${product.id}}">Edit</a>
                                &nbsp;&nbsp;&nbsp; <a th:href="@{'/delete/' + ${product.id}}">Delete</a>
                            </td>
                        </tr>
                    </th:block>
                    </tbody>
                </table>
            </div>

            <div>&nbsp; &nbsp;</div>

            <div>

                总计<span th:text="${totalItems}">99</span>行,当前显示<span th:text="(${currentPage} - 1)*${pageSize} +1">1</span> -
                <span th:text="(${currentPage} - 1)*${pageSize} +${numberOfElements}">5</span>行&nbsp; &nbsp;每页<select class="form-control pagination" id="pageSizeSelect">
                    <option th:each="selectedPageSize : ${pageSizes}" th:text="${selectedPageSize}"
                            th:value="${selectedPageSize}"
                            th:selected="${selectedPageSize} == ${pageSize}">5</option>
                </select>行
                &nbsp; &nbsp; 转到第<select class="form-control pagination" id="pageSelect">
                    <option th:each="i: ${#numbers.sequence(1, totalPages)}" th:text="${i}"
                            th:value="${i}"
                            th:selected="${i} == ${currentPage}">2</option>
                </select>页,总计<span th:text="${totalPages}">99</span>页 &nbsp; <a href="First.html" th:if="${currentPage > 1}"
                                                                 th:href="@{'/page/1?sortField=' + ${sortField} + '&sortDir=' + ${sortDir} +'&pageSize=' + ${pageSize}+ ${keyword != null ? '&keyword=' + keyword : ''}}">|&lt;最前页</a>
                <span th:unless="${currentPage > 1}">|&lt;最前页</span> &nbsp;&nbsp; <a href="Previous.html"
                                                                                        th:if="${currentPage > 1}"
                    th:href="@{'/page/' + ${currentPage - 1} + '?sortField=' + ${sortField} + '&sortDir=' + ${sortDir} +'&pageSize=' + ${pageSize}+ ${keyword != null ? '&keyword=' + keyword : ''}}">&lt;前一页</a>
                <span th:unless="${currentPage > 1}">&lt;前一页</span> &nbsp;&nbsp;  <a href="Next.html" th:if="${currentPage < totalPages}"
                                                                                     th:href="@{'/page/' + ${currentPage + 1} + '?sortField=' + ${sortField} + '&sortDir=' + ${sortDir} +'&pageSize=' + ${pageSize}+ ${keyword != null ? '&keyword=' + keyword : ''}}">后一页&gt;</a>
                <span th:unless="${currentPage < totalPages}">后一页&gt;</span>
                &nbsp;&nbsp; <a href="Last.html" th:if="${currentPage < totalPages}"
                                th:href="@{'/page/' + ${totalPages} + '?sortField=' + ${sortField} + '&sortDir=' + ${sortDir} +'&pageSize=' + ${pageSize}+ ${keyword != null ? '&keyword=' + keyword : ''}}">最后页&gt;|</a>
                <span th:unless="${currentPage < totalPages}">最后页&gt;|</span>
                &nbsp;&nbsp;

            </div>



            <script type="text/javascript" src="https://code.jquery.com/jquery-1.11.1.min.js"></script>
            <script type="text/javascript" src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/js/bootstrap.min.js"></script>
            <script type="text/javascript">
                            function clearSearch() {
                                window.location = "/";
                            }
            </script>
            <script type="text/javascript" th:inline="javascript">
                /*<![CDATA[*/
                $(document).ready(function () {
                    changePageSize();
                    changePage();
                });
                function changePageSize() {
                    $('#pageSizeSelect').change(function (evt) {
                        window.location.replace("/page/1/?sortField=id&sortDir=asc&pageSize=" + this.value);
                    });

                }
                /*]]>*/
            </script>

            <script th:inline="javascript">
                function changePage() {
                    $('#pageSelect').change(function (evt) {
                        window.location.replace("/page/" + this.value + "/?sortField=id&sortDir=asc&pageSize=" + [[${pageSize}]]);
                    });
                }
            </script>

        </div>
    </body>
</html>

new_product.html

<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml"
	xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="ISO-8859-1">
<title>Create new product</title>
</head>
<body>
	<div align="center">
		<h1>Create new product</h1>
		<br />
		<form action="#" th:action="@{/save}" th:object="${product}"
			method="post">

			<table border="0" cellpadding="10">
				<tr>
					<td>Product Name:</td>
					<td><input type="text" th:field="*{name}" /></td>
				</tr>
				<tr>
					<td>Brand</td>
					<td><input type="text" th:field="*{brand}" /></td>
				</tr>
				<tr>
					<td>Made in:</td>
					<td><input type="text" th:field="*{madein}" /></td>
				</tr>
				<tr>
					<td>Price:</td>
					<td><input type="text" th:field="*{price}" /></td>
				</tr>
				<tr>
					<td colspan="2"><button type="submit">Save</button></td>
				</tr>

			</table>
		</form>

	</div>
</body>
</html>

edit_product.html

<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml"
	xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="ISO-8859-1">
<title>Edit product</title>
</head>
<body>
	<div align="center">
		<h1>Edit product</h1>
		<br />
		<form action="#" th:action="@{/save}" th:object="${product}"
			method="post">
			<table border="0" cellpadding="10">
				<tr>
					<td>Product ID:</td>
					<td><input type="text" th:field="*{id}" readonly="readonly" />
					</td>
				</tr>
				<tr>
					<td>Product Name:</td>
					<td><input type="text" th:field="*{name}" /></td>
				</tr>
				<tr>
					<td>Brand</td>
					<td><input type="text" th:field="*{brand}" /></td>
				</tr>
				<tr>
					<td>Made in:</td>
					<td><input type="text" th:field="*{madein}" /></td>
				</tr>
				<tr>
					<td>Price:</td>
					<td><input type="text" th:field="*{price}" /></td>
				</tr>
				<tr>
					<td colspan="2"><button type="submit">Save</button></td>
				</tr>

			</table>
		</form>

	</div>
</body>
</html>

下载:GitHub - allwaysoft/Spring-Data-JDBC_Sorting_Paging_Search_Filter-Thymeleaf

<think>我们正在创建一个基于SpringBoot的名片管理系统。由于没有SpringInitializr,我们需要手动创建项目并配置依赖。步骤大致如下:1.创建Maven项目2.添加SpringBoot相关依赖3.配置数据库(MySQL)4.创建实体类(Entity)5.创建数据访问层(Repository)6.创建服务层(Service)7.创建控制器(Controller)8.创建视图(使用Thymeleaf模板引擎)9.配置应用程序属性我们将按照这些步骤详细说明。第一步:创建Maven项目在IntelliJIDEA中,选择File->New->Project,然后选择Maven,使用合适的JDK版本(例如JDK17),填写GroupId(如com.example)ArtifactId(如business-card-manager),然后创建项目。第二步:添加依赖在pom.xml中添加以下依赖:```xml<?xmlversion="1.0"encoding="UTF-8"?><projectxmlns="http://maven.apache.org/POM/4.0.0"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://maven.apache.org/POM/4.0.0http://maven.apache.org/xsd/maven-4.0.0.xsd"><modelVersion>4.0.0</modelVersion><groupId>com.example</groupId><artifactId>business-card-manager</artifactId><version>1.0-SNAPSHOT</version><parent><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-parent</artifactId><version>2.5.5</version><!--选择一个合适的SpringBoot版本--><relativePath/><!--从仓库查找,没有本地父项目--></parent><properties><java.version>17</java.version></properties><dependencies><!--SpringBootWebStarter用于构建Web应用--><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><!--SpringBootDataJPAStarter用于数据持久化--><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-jpa</artifactId></dependency><!--MySQL驱动--><dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId><scope>runtime</scope></dependency><!--Thymeleaf模板引擎--><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-thymeleaf</artifactId></dependency><!--SpringBootDevTools开发工具,支持热部署--><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-devtools</artifactId><optional>true</optional></dependency><!--Lombok简化代码--><dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId><optional>true</optional></dependency></dependencies><build><plugins><!--SpringBootMaven插件--><plugin><groupId>org.springframework.boot</groupId><artifactId>spring-boot-maven-plugin</artifactId></plugin></plugins></build></project>```第三步:配置数据库在src/main/resources目录下创建application.properties文件,配置数据库连接:```properties#服务器端口,默认8080server.port=8080#数据库配置spring.datasource.url=jdbc:mysql://localhost:3306/business_card_db?useSSL=false&serverTimezone=UTC&characterEncoding=UTF-8spring.datasource.username=rootspring.datasource.password=your_password#JPA配置spring.jpa.show-sql=truespring.jpa.hibernate.ddl-auto=updatespring.jpa.properties.hibernate.dialect=org.hibernate.dialect.MySQL5Dialect```注意:将your_password替换为你的MySQLroot密码,并确保已经创建了business_card_db数据库(或使用其他数据库名,相应地修改url)。第四步:创建实体类(Entity)在src/main/java/com/example/businesscardmanager目录下(根据你的包名调整)创建entity包,然后创建BusinessCard类:```javapackagecom.example.businesscardmanager.entity;importlombok.Data;importjavax.persistence.*;@Data@Entity@Table(name="business_card")publicclassBusinessCard{@Id@GeneratedValue(strategy=GenerationType.IDENTITY)privateLongid;@Column(nullable=false)privateStringname;@Column(nullable=false)privateStringphone;@Column(nullable=false)privateStringcompany;@Column(nullable=false)privateStringposition;@Column(nullable=false)privateStringemail;//可以根据需要扩展其他字段}```这里使用了Lombok的@Data注解自动生成getter、setter等方法。第五步:创建数据访问层(Repository)创建repository包,然后创建BusinessCardRepository接口:```javapackagecom.example.businesscardmanager.repository;importcom.example.businesscardmanager.entity.BusinessCard;importorg.springframework.data.jpa.repository.JpaRepository;importorg.springframework.stereotype.Repository;importjava.util.List;@RepositorypublicinterfaceBusinessCardRepositoryextendsJpaRepository<BusinessCard,Long>{//支持模糊查询:根据姓名查找List<BusinessCard>findByNameContaining(Stringname);}```第六步:创建服务层(Service)创建service包,然后创建BusinessCardService接口及其实现类。接口:```javapackagecom.example.businesscardmanager.service;importcom.example.businesscardmanager.entity.BusinessCard;importjava.util.List;publicinterfaceBusinessCardService{List<BusinessCard>findAll();BusinessCardsave(BusinessCardcard);BusinessCardfindById(Longid);voiddeleteById(Longid);List<BusinessCard>findByNameContaining(Stringname);}```实现类:```javapackagecom.example.businesscardmanager.service.impl;importcom.example.businesscardmanager.entity.BusinessCard;importcom.example.businesscardmanager.repository.BusinessCardRepository;importcom.example.businesscardmanager.service.BusinessCardService;importorg.springframework.beans.factory.annotation.Autowired;importorg.springframework.stereotype.Service;importjava.util.List;@ServicepublicclassBusinessCardServiceImplimplementsBusinessCardService{privatefinalBusinessCardRepositorycardRepository;@AutowiredpublicBusinessCardServiceImpl(BusinessCardRepositorycardRepository){this.cardRepository=cardRepository;}@OverridepublicList<BusinessCard>findAll(){returncardRepository.findAll();}@OverridepublicBusinessCardsave(BusinessCardcard){returncardRepository.save(card);}@OverridepublicBusinessCardfindById(Longid){returncardRepository.findById(id).orElse(null);}@OverridepublicvoiddeleteById(Longid){cardRepository.deleteById(id);}@OverridepublicList<BusinessCard>findByNameContaining(Stringname){returncardRepository.findByNameContaining(name);}}```第七步:创建控制器(Controller)创建controller包,然后创建BusinessCardController:```javapackagecom.example.businesscardmanager.controller;importcom.example.businesscardmanager.entity.BusinessCard;importcom.example.businesscardmanager.service.BusinessCardService;importorg.springframework.beans.factory.annotation.Autowired;importorg.springframework.stereotype.Controller;importorg.springframework.ui.Model;importorg.springframework.web.bind.annotation.*;@Controller@RequestMapping("/cards")publicclassBusinessCardController{privatefinalBusinessCardServicecardService;@AutowiredpublicBusinessCardController(BusinessCardServicecardService){this.cardService=cardService;}//显示所有名片@GetMappingpublicStringlistCards(Modelmodel){model.addAttribute("cards",cardService.findAll());return"cards/list";}//显示添加名片表单@GetMapping("/new")publicStringshowAddForm(Modelmodel){model.addAttribute("card",newBusinessCard());return"cards/form";}//添加名片@PostMappingpublicStringaddCard(@ModelAttributeBusinessCardcard){cardService.save(card);return"redirect:/cards";}//显示编辑表单@GetMapping("/edit/{id}")publicStringshowEditForm(@PathVariableLongid,Modelmodel){BusinessCardcard=cardService.findById(id);model.addAttribute("card",card);return"cards/form";}//更新名片@PostMapping("/update/{id}")publicStringupdateCard(@PathVariableLongid,@ModelAttributeBusinessCardcard){card.setId(id);cardService.save(card);return"redirect:/cards";}//删除名片@GetMapping("/delete/{id}")publicStringdeleteCard(@PathVariableLongid){cardService.deleteById(id);return"redirect:/cards";}//模糊查询@GetMapping("/search")publicStringsearchCards(@RequestParamStringkeyword,Modelmodel){List<BusinessCard>cards=cardService.findByNameContaining(keyword);model.addAttribute("cards",cards);return"cards/list";}}```第八步:创建视图(使用Thymeleaf)在src/main/resources/templates目录下创建cards目录,然后创建list.htmlform.html。list.html(列表页面,包含搜索名片列表):```html<!DOCTYPEhtml><htmlxmlns:th="http://www.thymeleaf.org"><head><metacharset="UTF-8"><title>名片列表</title><linkrel="stylesheet"href="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css"></head><body><divclass="container"><h1>名片管理</h1><div><ath:href="@{/cards/new}"class="btnbtn-primary">添加名片</a><formclass="form-inlinefloat-right"th:action="@{/cards/search}"method="get"><inputtype="text"name="keyword"class="form-control"placeholder="搜索姓名..."><buttontype="submit"class="btnbtn-secondaryml-2">搜索</button></form></div><tableclass="tabletable-striped"><thead><tr><th>ID</th><th>姓名</th><th>电话</th><th>公司</th><th>职位</th><th>邮箱</th><th>操作</th></tr></thead><tbody><trth:each="card:${cards}"><tdth:text="${card.id}"></td><tdth:text="${card.name}"></td><tdth:text="${card.phone}"></td><tdth:text="${card.company}"></td><tdth:text="${card.position}"></td><tdth:text="${card.email}"></td><td><ath:href="@{/cards/edit/{id}(id=${card.id})}"class="btnbtn-smbtn-warning">编辑</a><ath:href="@{/cards/delete/{id}(id=${card.id})}"class="btnbtn-smbtn-danger"onclick="returnconfirm('确定删除吗?')">删除</a></td></tr></tbody></table></div></body></html>```form.html(添加/编辑表单):```html<!DOCTYPEhtml><htmlxmlns:th="http://www.thymeleaf.org"><head><metacharset="UTF-8"><titleth:text="${card.id}?'编辑名片':'添加名片'"></title><linkrel="stylesheet"href="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css"></head><body><divclass="container"><h1th:text="${card.id}?'编辑名片':'添加名片'"></h1><formth:action="${card.id}?@{/cards/update/{id}(id=${card.id}):@{/cards}"th:object="${card}"method="post"><inputtype="hidden"th:field="*{id}"/><divclass="form-group"><label>姓名</label><inputtype="text"class="form-control"th:field="*{name}"required></div><divclass="form-group"><label>电话</label><inputtype="text"class="form-control"th:field="*{phone}"required></div><divclass="form-group"><label>公司</label><inputtype="text"class="form-control"th:field="*{company}"required></div><divclass="form-group"><label>职位</label><inputtype="text"class="form-control"th:field="*{position}"required></div><divclass="form-group"><label>邮箱</label><inputtype="email"class="form-control"th:field="*{email}"required></div><buttontype="submit"class="btnbtn-primary">保存</button><ath:href="@{/cards}"class="btnbtn-secondary">取消</a></form></div></body></html>```第九步:创建启动类在src/main/java/com/example/businesscardmanager目录下创建启动类:```javapackagecom.example.businesscardmanager;importorg.springframework.boot.SpringApplication;importorg.springframework.boot.autoconfigure.SpringBootApplication;@SpringBootApplicationpublicclassBusinessCardManagerApplication{publicstaticvoidmain(String[]args){SpringApplication.run(BusinessCardManagerApplication.class,args);}}```现在,运行BusinessCardManagerApplication的main方法,启动应用。访问:http://localhost:8080/cards注意:在运行前,请确保MySQL服务已启动,并且数据库business_card_db存在(或者根据配置的数据库名创建)。以上步骤详细说明了如何手动创建SpringBoot项目并实现名片管理系统的增删改查功能,包括模糊查询。同时,我们使用了Thymeleaf模板引擎来构建简单的Web界面。如果需要扩展字段,只需在BusinessCard实体类中添加新的属性对应的@Column注解即可。排序显示:在list.html中,可以通过表头的链接来实现排序,但这里没有实现。如果需要,可以在Repository中定义排序方法(如使用Sort参数)并在Service中调用,然后在Controller中传递排序后的列表。相关问题:1.如何为名片管理系统添加用户认证功能?2.如何实现名片数据的导出(如Excel)?3.如何优化名片管理系统的搜索功能(例如多字段搜索)?4.如何为名片管理系统添加分页功能?</think>### 名片管理系统开发指南(Java + Spring Boot) 下面将详细介绍使用JavaSpring Boot开发名片管理系统的完整步骤,包含增删改查、模糊查询、字段扩展排序显示功能。 --- ### 一、项目创建与配置(无Spring Initializr) #### 1. 创建Maven项目 在IntelliJ IDEA中: 1. `File → New → Project` 2. 选择`Maven` → 设置JDK版本(推荐JDK 11+) 3. 填写`GroupId`(如`com.example`)`ArtifactId`(如`business-card-system`) #### 2. 配置pom.xml ```xml <project> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.7.5</version> <!-- 兼容MySQL 5.7的版本 --> </parent> <dependencies> <!-- Web核心依赖 --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <!-- 数据库相关 --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-jpa</artifactId> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>8.0.33</version> <!-- 兼容MySQL 5.7的驱动 --> </dependency> <!-- 模板引擎 --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-thymeleaf</artifactId> </dependency> <!-- 开发工具 --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-devtools</artifactId> <optional>true</optional> </dependency> </dependencies> </project> ``` #### 3. 配置application.properties ```properties # 服务器端口 server.port=8080 # 数据库配置 spring.datasource.url=jdbc:mysql://localhost:3306/business_card_db?useSSL=false&characterEncoding=utf8 spring.datasource.username=root spring.datasource.password=your_password spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver # JPA配置 spring.jpa.show-sql=true spring.jpa.hibernate.ddl-auto=update spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.MySQL5Dialect ``` --- ### 二、数据库设计(MySQL 5.7.26) 创建数据库表: ```sql CREATE DATABASE business_card_db; USE business_card_db; CREATE TABLE business_card ( id INT AUTO_INCREMENT PRIMARY KEY, name VARCHAR(100) NOT NULL, company VARCHAR(100), position VARCHAR(50), phone VARCHAR(20), email VARCHAR(100), address VARCHAR(200), created_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP ); ``` --- ### 三、核心代码实现 #### 1. 实体类(Entity) ```java @Entity @Table(name = "business_card") public class BusinessCard { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; private String name; private String company; private String position; private String phone; private String email; private String address; @CreationTimestamp private LocalDateTime createdTime; // Getter/Setter省略(推荐使用Lombok) } ``` #### 2. 数据访问层(Repository) ```java public interface CardRepository extends JpaRepository<BusinessCard, Long> { // 模糊查询 @Query("SELECT c FROM BusinessCard c WHERE " + "c.name LIKE %:keyword% OR " + "c.company LIKE %:keyword% OR " + "c.position LIKE %:keyword%") List<BusinessCard> searchByKeyword(@Param("keyword") String keyword); // 排序查询 List<BusinessCard> findAll(Sort sort); } ``` #### 3. 服务层(Service) ```java @Service public class CardService { private final CardRepository cardRepository; // 增删改查方法 public BusinessCard saveCard(BusinessCard card) { return cardRepository.save(card); } public void deleteCard(Long id) { cardRepository.deleteById(id); } public List<BusinessCard> getAllCards(Sort sort) { return cardRepository.findAll(sort); } public List<BusinessCard> searchCards(String keyword) { return cardRepository.searchByKeyword(keyword); } } ``` #### 4. 控制器(Controller) ```java @Controller @RequestMapping("/cards") public class CardController { @GetMapping public String listCards( @RequestParam(required = false) String keyword, @RequestParam(defaultValue = "createdTime,desc") String sort, Model model ) { Sort sorting = Sort.by(Sort.Order.desc("createdTime")); if (sort.contains("name")) { sorting = Sort.by(sort.contains("desc") ? Sort.Order.desc("name") : Sort.Order.asc("name")); } List<BusinessCard> cards = keyword != null ? cardService.searchCards(keyword) : cardService.getAllCards(sorting); model.addAttribute("cards", cards); return "card-list"; } @PostMapping public String saveCard(@Valid BusinessCard card) { cardService.saveCard(card); return "redirect:/cards"; } } ``` #### 5. 视图层(Thymeleaf) `card-list.html` 关键部分: ```html <!-- 搜索排序表单 --> <form th:action="@{/cards}" method="get"> <input type="text" name="keyword" placeholder="输入关键词"> <select name="sort"> <option value="name,asc">姓名升序</option> <option value="name,desc">姓名降序</option> </select> <button type="submit">搜索</button> </form> <!-- 名片列表 --> <table> <tr th:each="card : ${cards}"> <td th:text="${card.name}"></td> <td th:text="${card.company}"></td> <td th:text="${card.position}"></td> <td> <a th:href="@{/cards/edit/{id}(id=${card.id})}">编辑</a> <a th:href="@{/cards/delete/{id}(id=${card.id})}">删除</a> </td> </tr> </table> ``` --- ### 四、字段扩展实现 要添加新字段(如`wechat`): 1. 在实体类中添加属性: ```java private String wechat; ``` 2. 数据库会自动更新(ddl-auto=update) 3. 在表单列表中增加对应字段 --- ### 五、运行与测试 1. 启动MySQL服务 2. 运行Spring Boot主类 3. 访问:`http://localhost:8080/cards` 4. 测试功能: - 添加新名片 - 按姓名/公司模糊搜索 - 按创建时间/姓名排序 - 编辑/删除操作 --- ### 常见问题解决 1. **MySQL连接问题**: - 确认`serverTimezone=UTC`参数 - 检查MySQL用户权限 2. **字段扩展失败**: - 手动执行ALTER TABLE添加字段 - 检查实体类属性命名规范 3. **排序不生效**: - 检查Sort参数格式:`fieldName,direction` - 确认Repository方法正确调用Sort > 提示:使用Spring Data JPA的`@DynamicUpdate`注解可优化更新操作[^1] --- ### 相关技术拓展问题 1. 如何实现名片数据的Excel导入导出功能? 2. 如何在Spring Boot中集成Redis缓存提升查询性能? 3. 如何为名片管理系统添加用户认证(如JWT)? 4. 如何实现名片二维码生成与扫描功能? 5. 如何进行Spring Boot应用的Docker容器化部署? [^1]: Spring Data JPA动态更新机制 [^2]: MySQL 5.7与Java 11兼容性指南 [^3]: Thymeleaf模板引擎最佳实践
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值