How to Insert Data into Multiple Tables Using Spring JPA (The Right Way)

How to Insert Data into Multiple Tables Using Spring JPA

Introduction

When working with relational databases in Spring JPA, you’ll often need to insert data into multiple related tables—for example, creating a new User along with a corresponding Transaction (like a default “SEND MONEY” record).

If you’ve been manually setting foreign keys or saving entities separately, this guide will help you do it the Spring JPA way using proper entity relationships, cascade operations, and best practices for transactional integrity.


Why Your Current Approach Might Be Failing

If you’re trying to set user_id manually in the Transaction table without a defined JPA relationship, JPA won’t know how to manage the foreign key or ensure consistency. This leads to:

  • Foreign key errors
  • Redundant save operations
  • Data integrity issues

Step 1: Define the Entity Relationship

To make Spring JPA handle inserts across tables automatically, model a bidirectional one-to-many relationship between User and Transaction.

User Entity:

@Entity
@Table(name = "user")
public class User {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    // Other fields like name, email, etc.

    @OneToMany(mappedBy = "user", cascade = CascadeType.ALL)
    private Set<Transaction> transactions = new HashSet<>();

    public void addTransaction(Transaction transaction) {
        transactions.add(transaction);
        transaction.setUser(this); // Set reverse link
    }

    // Getters and setters
}

Transaction Entity:

@Entity
@Table(name = "transaction")
public class Transaction {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long idTrans;

    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "user_id")
    private User user;

    private String type;
    private Timestamp transactionDate;
    private int transMoney;

    // Getters and setters
}

Important Notes:

  • @OneToMany with cascade = CascadeType.ALL ensures all child transactions are saved with the parent.
  • The addTransaction() method ensures bidirectional integrity.

Step 2: Create the Repositories

Use Spring Data JPA interfaces:

public interface UserRepository extends JpaRepository<User, Long> {}
public interface TransactionRepository extends JpaRepository<Transaction, Long> {}

Step 3: Build the Service Layer

Create both entities in one go inside a service method. No need to call transactionRepository.save() separately.

@Service
public class UserService {
    private final UserRepository userRepository;

    public UserService(UserRepository userRepository) {
        this.userRepository = userRepository;
    }

    @Transactional
    public User createUserWithTransaction(UserRequest userRequest) {
        User user = new User();
        user.setName(userRequest.getName());
        user.setEmail(userRequest.getEmail());

        Transaction transaction = new Transaction();
        transaction.setType("SEND MONEY");
        transaction.setTransactionDate(new Timestamp(System.currentTimeMillis()));
        transaction.setTransMoney(userRequest.getInitialMoney());

        user.addTransaction(transaction); // Associate transaction

        return userRepository.save(user); // Persist both entities
    }
}

Step 4: Create the REST Controller

Create a simple endpoint to accept user data and trigger the creation process:

@RestController
@RequestMapping("/users")
public class UserController {
    private final UserService userService;

    public UserController(UserService userService) {
        this.userService = userService;
    }

    @PostMapping
    public ResponseEntity<User> createUser(@RequestBody UserRequest request) {
        User savedUser = userService.createUserWithTransaction(request);
        return ResponseEntity.ok(savedUser);
    }
}

Common Pitfalls & How to Fix Them

  1. LazyInitializationException:
    Wrap your service methods with @Transactional to ensure lazy-loaded fields are accessible.
  2. Cascading Doesn’t Work:
    Ensure CascadeType.ALL is specified on the @OneToMany mapping.
  3. Missing Reverse Mapping:
    Always update both sides of the relationship. Using user.addTransaction() handles this.
  4. JSON Infinite Recursion:
    Use @JsonIgnore on the user field in Transaction or manage with @JsonManagedReference / @JsonBackReference.

Best Practices

✅ Use DTOs (like UserRequest) to decouple entity logic from API inputs/outputs
✅ Annotate service methods with @Transactional for atomic operations
✅ Initialize collections (like Set<Transaction>) to avoid NullPointerException
✅ Avoid manual foreign key assignments—let JPA manage it
✅ Use meaningful names and relationships to reflect your domain logic


Keywords:

Spring Data JPA insert multiple tables, one-to-many relationship, JPA cascade persist, save parent and child entity, bidirectional mapping JPA, Spring Boot transactional insert, JPA entity association


Conclusion

Inserting data into multiple tables in Spring JPA doesn’t need to be messy. With well-defined entity relationships, cascading, and proper service layering, you can persist parent-child records like User and Transaction in one smooth operation—cleanly and reliably.

Want to explore more? Check out the official Spring Data JPA documentation or our REST API design guide for additional insights. 🚀


Let me know if you’d like to turn this into a downloadable PDF, add diagram illustrations, or generate a sample project template!

Comments

No comments yet. Why don’t you start the discussion?

Leave a Reply

Your email address will not be published. Required fields are marked *