在JSF中复用JDBC数据源实现多Bean数据库访问的问题
Alright, let's break down why you're hitting this issue first: Each @DataSourceDefinition annotation on a bean class creates a separate, independent data source instance—even if all the configuration parameters are identical. That means your UserRepo and Customer bean are using two completely separate connection pools under the hood, which can lead to unexpected behavior like inconsistent query results, transaction context mismatches, or connection pool state issues.
The fix is to define your data source once at the application level, then inject that single shared instance into all your beans. Here are three robust approaches to do this:
1. Global @DataSourceDefinition on an Application-Scoped Class
Instead of attaching @DataSourceDefinition to each repository/bean, place it on a dedicated application-scoped configuration class. This ensures only one data source instance is created for the entire app.
Step 1: Create a Global Data Source Config
import javax.annotation.sql.DataSourceDefinition; import javax.enterprise.context.ApplicationScoped; @ApplicationScoped @DataSourceDefinition( name = "java:app/jdbc/SharedDB", // Unique JNDI name className = "com.mysql.cj.jdbc.MysqlDataSource", // Use your DB driver class url = "jdbc:mysql://localhost:3306/your_db", user = "db_user", password = "db_password", // Optional: Configure connection pool settings maxPoolSize = 10, minPoolSize = 2 ) public class SharedDataSourceConfig { // No additional code needed—this class just hosts the data source definition }
Step 2: Inject the Shared Data Source into Your Beans
Now inject the same data source into both UserRepo and Customer bean using its JNDI name:
import javax.annotation.Resource; import javax.enterprise.context.RequestScoped; import javax.sql.DataSource; @RequestScoped public class UserRepo { @Resource(lookup = "java:app/jdbc/SharedDB") private DataSource dataSource; // Your query methods here (use this data source for connections) }
@RequestScoped public class CustomerBean { @Resource(lookup = "java:app/jdbc/SharedDB") private DataSource dataSource; // Your query methods here—same shared data source as UserRepo }
2. CDI Producer Method for Flexible Configuration
If you need more control (like reading credentials from a config file, or programmatically setting connection pool parameters), use a CDI producer to create and expose the shared data source.
Step 1: Create a Data Source Producer
import javax.enterprise.context.ApplicationScoped; import javax.enterprise.inject.Produces; import javax.sql.DataSource; import com.mysql.cj.jdbc.MysqlDataSource; @ApplicationScoped public class DataSourceProducer { @Produces @ApplicationScoped public DataSource createSharedDataSource() { MysqlDataSource dataSource = new MysqlDataSource(); dataSource.setUrl("jdbc:mysql://localhost:3306/your_db"); dataSource.setUser("db_user"); dataSource.setPassword("db_password"); // Configure connection pool settings as needed dataSource.setMaxPoolSize(10); dataSource.setConnectionTimeout(5000); return dataSource; } }
Step 2: Inject the Produced Data Source
Now you can inject the data source directly with @Inject (no JNDI lookup needed):
import javax.inject.Inject; import javax.enterprise.context.RequestScoped; import javax.sql.DataSource; @RequestScoped public class UserRepo { @Inject private DataSource dataSource; // Query logic here }
@RequestScoped public class CustomerBean { @Inject private DataSource dataSource; // Query logic here—same shared data source }
3. Container-Managed Data Source (For Servlet Containers like Tomcat)
If you're using a servlet container (e.g., Tomcat, Jetty), you can configure the data source directly in the container's configuration files. This is ideal for production environments where you want to separate DB config from your application code.
Step 1: Configure Data Source in context.xml
Add this to your Tomcat conf/context.xml (or a project-specific META-INF/context.xml):
<Resource name="jdbc/SharedDB" auth="Container" type="javax.sql.DataSource" driverClassName="com.mysql.cj.jdbc.Driver" url="jdbc:mysql://localhost:3306/your_db" username="db_user" password="db_password" maxTotal="10" maxIdle="5" maxWaitMillis="5000"/>
Step 2: Reference the Data Source in web.xml
Add a resource reference to your app's web.xml:
<resource-ref> <description>Shared Database Connection</description> <res-ref-name>jdbc/SharedDB</res-ref-name> <res-type>javax.sql.DataSource</res-type> <res-auth>Container</res-auth> </resource-ref>
Step 3: Inject the Container-Managed Data Source
Inject it into your beans using the JNDI lookup:
@Resource(lookup = "java:comp/env/jdbc/SharedDB") private DataSource dataSource;
Why Your Original Approach Failed
When you put @DataSourceDefinition on each bean, the CDI container creates a unique data source instance for each bean class. Even with identical configs, these are separate connection pools—so your Customer bean was using a different pool than UserRepo, which could lead to issues like uncommitted transactions, stale connections, or inconsistent query results depending on your DB state.
By centralizing the data source definition, you ensure all beans use the same connection pool and data source instance, eliminating these inconsistencies while keeping your beans modular (no "god class" required!).
内容的提问来源于stack exchange,提问作者user9782268

